mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2024-12-29 09:22:22 -05:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
506e7c036c
19 changed files with 1095 additions and 213 deletions
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* The Point object represents a point in the two dimensional space of the
|
||||
* Paper.js document. It is also used to represent two dimensional vector
|
||||
* objects.
|
||||
*/
|
||||
var Point = Base.extend({
|
||||
beans: true,
|
||||
|
||||
|
@ -28,6 +33,21 @@ var Point = Base.extend({
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a copy of the point.
|
||||
* This is useful as the following code only generates a flat copy:
|
||||
*
|
||||
* <code>
|
||||
* var point1 = new Point();
|
||||
* var point2 = point1;
|
||||
* point2.x = 1; // also changes point1.x
|
||||
*
|
||||
* var point2 = point1.clone();
|
||||
* point2.x = 1; // doesn't change point1.x
|
||||
* </code>
|
||||
*
|
||||
* @return the cloned point
|
||||
*/
|
||||
clone: function() {
|
||||
return Point.create(this.x, this.y);
|
||||
},
|
||||
|
@ -66,6 +86,26 @@ var Point = Base.extend({
|
|||
return this.x == point.x && this.y == point.y;
|
||||
},
|
||||
|
||||
transform: function(matrix) {
|
||||
return matrix.transform(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the distance between the point and another point.
|
||||
*
|
||||
* Sample code:
|
||||
* <code>
|
||||
* var firstPoint = new Point(5, 10);
|
||||
* var secondPoint = new Point(5, 20);
|
||||
*
|
||||
* var distance = firstPoint.getDistance(secondPoint);
|
||||
*
|
||||
* print(distance); // 10
|
||||
* </code>
|
||||
*
|
||||
* @param px
|
||||
* @param py
|
||||
*/
|
||||
getDistance: function() {
|
||||
var point = Point.read(arguments);
|
||||
var px = point.x - this.x;
|
||||
|
@ -80,6 +120,12 @@ var Point = Base.extend({
|
|||
return px * px + py * py;
|
||||
},
|
||||
|
||||
/**
|
||||
* The length of the vector that is represented by this point's coordinates.
|
||||
* Each point can be interpreted as a vector that points from the origin
|
||||
* ({@code x = 0},{@code y = 0}) to the point's location.
|
||||
* Setting the length changes the location but keeps the vector's angle.
|
||||
*/
|
||||
getLength: function() {
|
||||
var point = Point.read(arguments);
|
||||
return Math.sqrt(this.x * this.x + this.y * this.y);
|
||||
|
@ -131,7 +177,6 @@ var Point = Base.extend({
|
|||
return Math.atan2(this.y, this.x) * 180 / Math.PI;
|
||||
},
|
||||
|
||||
|
||||
getQuadrant: function() {
|
||||
if (this.x >= 0) {
|
||||
if (this.y >= 0) {
|
||||
|
@ -148,15 +193,20 @@ var Point = Base.extend({
|
|||
}
|
||||
},
|
||||
|
||||
setAngle: function(angle) {
|
||||
angle = this._angle = angle * Math.PI / 180;
|
||||
if (!this.isZero()) {
|
||||
var length = this.length;
|
||||
this.x = Math.cos(angle) * length;
|
||||
this.y = Math.sin(angle) * length;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* {@grouptitle Angle & Rotation}
|
||||
*
|
||||
* The vector's angle, measured from the x-axis to the vector.
|
||||
*
|
||||
* When supplied with a point, returns the smaller angle between two
|
||||
* vectors. The angle is unsigned, no information about rotational
|
||||
* direction is given.
|
||||
*
|
||||
* Read more about angle units and orientation in the description of the
|
||||
* {@link #getAngle()} property.
|
||||
*
|
||||
* @param point
|
||||
*/
|
||||
getAngle: function() {
|
||||
var angle;
|
||||
if (arguments.length) {
|
||||
|
@ -175,6 +225,24 @@ var Point = Base.extend({
|
|||
return angle * 180 / Math.PI;
|
||||
},
|
||||
|
||||
setAngle: function(angle) {
|
||||
angle = this._angle = angle * Math.PI / 180;
|
||||
if (!this.isZero()) {
|
||||
var length = this.length;
|
||||
this.x = Math.cos(angle) * length;
|
||||
this.y = Math.sin(angle) * length;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the angle between two vectors. The angle is directional and
|
||||
* signed, giving information about the rotational direction.
|
||||
*
|
||||
* Read more about angle units and orientation in the description of the
|
||||
* {@link #getAngle()} property.
|
||||
*
|
||||
* @param point
|
||||
*/
|
||||
getDirectedAngle: function() {
|
||||
var point = Point.read(arguments);
|
||||
var angle = this.angle - point.angle;
|
||||
|
@ -188,7 +256,17 @@ var Point = Base.extend({
|
|||
}
|
||||
},
|
||||
|
||||
// TODO: Add center parameter support back to Scriptographer
|
||||
/**
|
||||
* Rotates the point by the given angle around an optional center point.
|
||||
* The object itself is not modified.
|
||||
*
|
||||
* Read more about angle units and orientation in the description of the
|
||||
* {@link #getAngle()} property.
|
||||
*
|
||||
* @param angle the rotation angle
|
||||
* @param center the center point of the rotation
|
||||
* @return the rotated point
|
||||
*/
|
||||
rotate: function(angle, center) {
|
||||
var point = center ? this.subtract(center) : this;
|
||||
angle = angle * Math.PI / 180;
|
||||
|
@ -201,6 +279,16 @@ var Point = Base.extend({
|
|||
return center ? point.add(center) : point;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the interpolation point between the point and another point.
|
||||
* The object itself is not modified!
|
||||
*
|
||||
* @param point
|
||||
* @param t the position between the two points as a value between 0 and 1
|
||||
* @return the interpolation point
|
||||
*
|
||||
* @jshide
|
||||
*/
|
||||
interpolate: function(point, t) {
|
||||
return Point.create(
|
||||
this.x * (1 - t) + point.x * t,
|
||||
|
@ -208,52 +296,152 @@ var Point = Base.extend({
|
|||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* {@grouptitle Tests}
|
||||
*
|
||||
* Checks whether the point is inside the boundaries of the rectangle.
|
||||
*
|
||||
* @param rect the rectangle to check against
|
||||
* @return {@true if the point is inside the rectangle}
|
||||
*/
|
||||
isInside: function(rect) {
|
||||
return rect.contains(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the point is within a given distance of another point.
|
||||
*
|
||||
* @param point the point to check against
|
||||
* @param tolerance the maximum distance allowed
|
||||
* @return {@true if it is within the given distance}
|
||||
*/
|
||||
isClose: function(point, tolerance) {
|
||||
return this.getDistance(point) < tolerance;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the vector represented by this point is parallel (collinear) to
|
||||
* another vector.
|
||||
*
|
||||
* @param point the vector to check against
|
||||
* @return {@true if it is parallel}
|
||||
*/
|
||||
isParallel: function(point) {
|
||||
return Math.abs(this.x / point.x - this.y / point.y) < 0.00001;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if this point has both the x and y coordinate set to 0.
|
||||
*
|
||||
* @return {@true if both x and y are 0}
|
||||
*/
|
||||
isZero: function() {
|
||||
return this.x == 0 && this.y == 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if this point has an undefined value for at least one of its
|
||||
* coordinates.
|
||||
*
|
||||
* @return {@true if either x or y are not a number}
|
||||
*/
|
||||
isNaN: function() {
|
||||
return isNaN(this.x) || isNaN(this.y);
|
||||
},
|
||||
|
||||
/**
|
||||
* {@grouptitle Math Functions}
|
||||
*
|
||||
* Returns a new point with rounded {@link #x} and {@link #y} values. The
|
||||
* object itself is not modified!
|
||||
*
|
||||
* Sample code:
|
||||
* <code>
|
||||
* var point = new Point(10.2, 10.9);
|
||||
* var roundPoint = point.round();
|
||||
* print(roundPoint); // { x: 10.0, y: 11.0 }
|
||||
* </code>
|
||||
*/
|
||||
round: function() {
|
||||
return Point.create(Math.round(this.x), Math.round(this.y));
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a new point with the nearest greater non-fractional values to the
|
||||
* specified {@link #x} and {@link #y} values. The object itself is not
|
||||
* modified!
|
||||
*
|
||||
* Sample code:
|
||||
* <code>
|
||||
* var point = new Point(10.2, 10.9);
|
||||
* var ceilPoint = point.ceil();
|
||||
* print(ceilPoint); // { x: 11.0, y: 11.0 }
|
||||
* </code>
|
||||
*/
|
||||
ceil: function() {
|
||||
return Point.create(Math.ceil(this.x), Math.ceil(this.y));
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a new point with the nearest smaller non-fractional values to the
|
||||
* specified {@link #x} and {@link #y} values. The object itself is not
|
||||
* modified!
|
||||
*
|
||||
* Sample code:
|
||||
* <code>
|
||||
* var point = new Point(10.2, 10.9);
|
||||
* var floorPoint = point.floor();
|
||||
* print(floorPoint); // { x: 10.0, y: 10.0 }
|
||||
* </code>
|
||||
*/
|
||||
floor: function() {
|
||||
return Point.create(Math.floor(this.x), Math.floor(this.y));
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a new point with the absolute values of the specified {@link #x}
|
||||
* and {@link #y} values. The object itself is not modified!
|
||||
*
|
||||
* Sample code:
|
||||
* <code>
|
||||
* var point = new Point(-5, 10);
|
||||
* var absPoint = point.abs();
|
||||
* print(absPoint); // { x: 5.0, y: 10.0 }
|
||||
* </code>
|
||||
*/
|
||||
abs: function() {
|
||||
return Point.create(Math.abs(this.x), Math.abs(this.y));
|
||||
},
|
||||
|
||||
/**
|
||||
* {@grouptitle Vectorial Math Functions}
|
||||
*
|
||||
* Returns the dot product of the point and another point.
|
||||
* @param point
|
||||
* @return the dot product of the two points
|
||||
*/
|
||||
dot: function() {
|
||||
var point = Point.read(arguments);
|
||||
return this.x * point.x + this.y * point.y;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the cross product of the point and another point.
|
||||
* @param point
|
||||
* @return the cross product of the two points
|
||||
*/
|
||||
cross: function() {
|
||||
var point = Point.read(arguments);
|
||||
return this.x * point.y - this.y - point.x;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the projection of the point on another point.
|
||||
* Both points are interpreted as vectors.
|
||||
*
|
||||
* @param point
|
||||
* @return the project of the point on another point
|
||||
*/
|
||||
project: function() {
|
||||
var point = Point.read(arguments);
|
||||
if (point.isZero()) {
|
||||
|
@ -297,18 +485,63 @@ var Point = Base.extend({
|
|||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a new point object with the smallest {@link #x} and
|
||||
* {@link #y} of the supplied points.
|
||||
*
|
||||
* Sample code:
|
||||
* <code>
|
||||
* var point1 = new Point(10, 100);
|
||||
* var point2 = new Point(200, 5);
|
||||
* var minPoint = Point.min(point1, point2);
|
||||
* print(minPoint); // { x: 10.0, y: 5.0 }
|
||||
* </code>
|
||||
*
|
||||
* @param point1
|
||||
* @param point2
|
||||
* @return The newly created point object
|
||||
*/
|
||||
min: function(point1, point2) {
|
||||
return Point.create(
|
||||
Math.min(point1.x, point2.x),
|
||||
Math.min(point1.y, point2.y));
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a new point object with the largest {@link #x} and
|
||||
* {@link #y} of the supplied points.
|
||||
*
|
||||
* Sample code:
|
||||
* <code>
|
||||
* var point1 = new Point(10, 100);
|
||||
* var point2 = new Point(200, 5);
|
||||
* var maxPoint = Point.max(point1, point2);
|
||||
* print(maxPoint); // { x: 200.0, y: 100.0 }
|
||||
* </code>
|
||||
*
|
||||
* @param point1
|
||||
* @param point2
|
||||
* @return The newly created point object
|
||||
*/
|
||||
max: function(point1, point2) {
|
||||
return Point.create(
|
||||
Math.max(point1.x, point2.x),
|
||||
Math.max(point1.y, point2.y));
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a point object with random {@link #x} and {@link #y} values
|
||||
* between {@code 0} and {@code 1}.
|
||||
*
|
||||
* Sample code:
|
||||
* <code>
|
||||
* var maxPoint = new Point(100, 100);
|
||||
* var randomPoint = Point.random();
|
||||
*
|
||||
* // A point between {x:0, y:0} and {x:100, y:100}:
|
||||
* var point = maxPoint * randomPoint;
|
||||
* </code>
|
||||
*/
|
||||
random: function() {
|
||||
return Point.create(Math.random(), Math.random());
|
||||
}
|
||||
|
|
|
@ -157,7 +157,7 @@ RGBColor = Color.extend(new function() {
|
|||
|
||||
setGray: function(gray) {
|
||||
this._cssString = null;
|
||||
this._red = this._green = this._blue = gray;
|
||||
this._red = this._green = this._blue = 1 - gray;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -29,11 +29,12 @@ Doc = Base.extend({
|
|||
|
||||
redraw: function() {
|
||||
if (this.canvas) {
|
||||
// TODO: clearing the canvas by setting
|
||||
// this.canvas.width = this.canvas.width might be faster..
|
||||
this.ctx.clearRect(0, 0, this.size.width + 1, this.size.height);
|
||||
// Initial tests conclude that clearing the canvas is always
|
||||
// faster than using clearRect:
|
||||
// http://jsperf.com/clearrect-vs-setting-width/7
|
||||
this.ctx.clearRect(0, 0, this.size.width + 1, this.size.height + 1);
|
||||
for (var i = 0, l = this.layers.length; i < l; i++) {
|
||||
this.layers[i].draw(this.ctx);
|
||||
this.layers[i].draw(this.ctx, {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
215
src/item/BlendMode.js
Normal file
215
src/item/BlendMode.js
Normal file
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* BlendMode code ported from Context Blender JavaScript Library
|
||||
*
|
||||
* Copyright © 2010 Gavin Kistner
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
BlendMode = {
|
||||
// TODO: Should we remove the blend modes that are not in Scriptographer?
|
||||
// TODO: Add missing blendmodes like hue / saturation / color / luminosity
|
||||
// TODO: Clean up codespacing of original code, or keep it as is, so
|
||||
// we can easily encorporate changes?
|
||||
process: function(documentContext, item, param) {
|
||||
// TODO: use strokeBounds
|
||||
var itemBounds = item.bounds;
|
||||
var top = Math.floor(itemBounds.top);
|
||||
var left = Math.floor(itemBounds.left);
|
||||
var size = itemBounds.size.ceil();
|
||||
var width = size.width;
|
||||
var height = size.height;
|
||||
|
||||
var itemCanvas = CanvasProvider.getCanvas(size);
|
||||
var itemContext = itemCanvas.getContext('2d');
|
||||
if(item.matrix) {
|
||||
var matrix = item.matrix.clone();
|
||||
var transMatrix = Matrix.getTranslateInstance(-left, -top);
|
||||
matrix.preConcatenate(transMatrix);
|
||||
// TODO: Profiling shows this as a hotspot
|
||||
matrix.applyToContext(itemContext);
|
||||
} else {
|
||||
itemContext.translate(-itemBounds.left, -itemBounds.top);
|
||||
}
|
||||
param.ignoreBlendMode = true;
|
||||
item.draw(itemContext, param);
|
||||
|
||||
var dstD = documentContext.getImageData(
|
||||
left, top,
|
||||
width, height
|
||||
);
|
||||
|
||||
var srcD = itemContext.getImageData(
|
||||
0, 0,
|
||||
width, height
|
||||
);
|
||||
|
||||
var src = srcD.data;
|
||||
var dst = dstD.data;
|
||||
var sA, dA, len=dst.length;
|
||||
var sRA, sGA, sBA, dRA, dGA, dBA, dA2;
|
||||
var demultiply;
|
||||
|
||||
for (var px=0;px<len;px+=4){
|
||||
sA = src[px+3]/255;
|
||||
dA = dst[px+3]/255;
|
||||
dA2 = (sA + dA - sA*dA);
|
||||
dst[px+3] = dA2*255;
|
||||
|
||||
sRA = src[px ]/255*sA;
|
||||
dRA = dst[px ]/255*dA;
|
||||
sGA = src[px+1]/255*sA;
|
||||
dGA = dst[px+1]/255*dA;
|
||||
sBA = src[px+2]/255*sA;
|
||||
dBA = dst[px+2]/255*dA;
|
||||
|
||||
demultiply = 255 / dA2;
|
||||
|
||||
switch(item.blendMode){
|
||||
// ******* Very close match to Photoshop
|
||||
case 'normal':
|
||||
case 'src-over':
|
||||
dst[px ] = (sRA + dRA - dRA*sA) * demultiply;
|
||||
dst[px+1] = (sGA + dGA - dGA*sA) * demultiply;
|
||||
dst[px+2] = (sBA + dBA - dBA*sA) * demultiply;
|
||||
break;
|
||||
|
||||
case 'screen':
|
||||
dst[px ] = (sRA + dRA - sRA*dRA) * demultiply;
|
||||
dst[px+1] = (sGA + dGA - sGA*dGA) * demultiply;
|
||||
dst[px+2] = (sBA + dBA - sBA*dBA) * demultiply;
|
||||
break;
|
||||
|
||||
case 'multiply':
|
||||
dst[px ] = (sRA*dRA + sRA*(1-dA) + dRA*(1-sA)) * demultiply;
|
||||
dst[px+1] = (sGA*dGA + sGA*(1-dA) + dGA*(1-sA)) * demultiply;
|
||||
dst[px+2] = (sBA*dBA + sBA*(1-dA) + dBA*(1-sA)) * demultiply;
|
||||
break;
|
||||
|
||||
case 'difference':
|
||||
dst[px ] = (sRA + dRA - 2 * Math.min( sRA*dA, dRA*sA )) * demultiply;
|
||||
dst[px+1] = (sGA + dGA - 2 * Math.min( sGA*dA, dGA*sA )) * demultiply;
|
||||
dst[px+2] = (sBA + dBA - 2 * Math.min( sBA*dA, dBA*sA )) * demultiply;
|
||||
break;
|
||||
|
||||
// ******* Slightly different from Photoshop, where alpha is concerned
|
||||
case 'src-in':
|
||||
// Only differs from Photoshop in low-opacity areas
|
||||
dA2 = sA*dA;
|
||||
demultiply = 255 / dA2;
|
||||
dst[px+3] = dA2*255;
|
||||
dst[px ] = sRA*dA * demultiply;
|
||||
dst[px+1] = sGA*dA * demultiply;
|
||||
dst[px+2] = sBA*dA * demultiply;
|
||||
break;
|
||||
|
||||
case 'plus':
|
||||
case 'add':
|
||||
// Photoshop doesn't simply add the alpha channels; this might be correct wrt SVG 1.2
|
||||
dA2 = Math.min(1,sA+dA);
|
||||
dst[px+3] = dA2*255;
|
||||
demultiply = 255 / dA2;
|
||||
dst[px ] = Math.min(sRA + dRA,1) * demultiply;
|
||||
dst[px+1] = Math.min(sGA + dGA,1) * demultiply;
|
||||
dst[px+2] = Math.min(sBA + dBA,1) * demultiply;
|
||||
break;
|
||||
|
||||
case 'overlay':
|
||||
// Correct for 100% opacity case; colors get clipped as opacity falls
|
||||
dst[px ] = (dRA<=0.5) ? (2*src[px ]*dRA/dA) : 255 - (2 - 2*dRA/dA) * (255-src[px ]);
|
||||
dst[px+1] = (dGA<=0.5) ? (2*src[px+1]*dGA/dA) : 255 - (2 - 2*dGA/dA) * (255-src[px+1]);
|
||||
dst[px+2] = (dBA<=0.5) ? (2*src[px+2]*dBA/dA) : 255 - (2 - 2*dBA/dA) * (255-src[px+2]);
|
||||
|
||||
// http://dunnbypaul.net/blends/
|
||||
// dst[px ] = ( (dRA<=0.5) ? (2*sRA*dRA) : 1 - (1 - 2*(dRA-0.5)) * (1-sRA) ) * demultiply;
|
||||
// dst[px+1] = ( (dGA<=0.5) ? (2*sGA*dGA) : 1 - (1 - 2*(dGA-0.5)) * (1-sGA) ) * demultiply;
|
||||
// dst[px+2] = ( (dBA<=0.5) ? (2*sBA*dBA) : 1 - (1 - 2*(dBA-0.5)) * (1-sBA) ) * demultiply;
|
||||
|
||||
// http://www.barbato.us/2010/12/01/blimageblending-emulating-photoshops-blending-modes-opencv/#toc-blendoverlay
|
||||
// dst[px ] = ( (sRA<=0.5) ? (sRA*dRA + sRA*(1-dA) + dRA*(1-sA)) : (sRA + dRA - sRA*dRA) ) * demultiply;
|
||||
// dst[px+1] = ( (sGA<=0.5) ? (sGA*dGA + sGA*(1-dA) + dGA*(1-sA)) : (sGA + dGA - sGA*dGA) ) * demultiply;
|
||||
// dst[px+2] = ( (sBA<=0.5) ? (sBA*dBA + sBA*(1-dA) + dBA*(1-sA)) : (sBA + dBA - sBA*dBA) ) * demultiply;
|
||||
|
||||
// http://www.nathanm.com/photoshop-blending-math/
|
||||
// dst[px ] = ( (sRA < 0.5) ? (2 * dRA * sRA) : (1 - 2 * (1 - sRA) * (1 - dRA)) ) * demultiply;
|
||||
// dst[px+1] = ( (sGA < 0.5) ? (2 * dGA * sGA) : (1 - 2 * (1 - sGA) * (1 - dGA)) ) * demultiply;
|
||||
// dst[px+2] = ( (sBA < 0.5) ? (2 * dBA * sBA) : (1 - 2 * (1 - sBA) * (1 - dBA)) ) * demultiply;
|
||||
break;
|
||||
|
||||
case 'hardlight':
|
||||
dst[px ] = (sRA<=0.5) ? (2*dst[px ]*sRA/dA) : 255 - (2 - 2*sRA/sA) * (255-dst[px ]);
|
||||
dst[px+1] = (sGA<=0.5) ? (2*dst[px+1]*sGA/dA) : 255 - (2 - 2*sGA/sA) * (255-dst[px+1]);
|
||||
dst[px+2] = (sBA<=0.5) ? (2*dst[px+2]*sBA/dA) : 255 - (2 - 2*sBA/sA) * (255-dst[px+2]);
|
||||
break;
|
||||
|
||||
case 'color-dodge':
|
||||
case 'dodge':
|
||||
if ( src[px ] == 255 && dRA==0) dst[px ] = 255;
|
||||
else dst[px ] = Math.min(255, dst[px ]/(255 - src[px ])) * demultiply;
|
||||
|
||||
if ( src[px+1] == 255 && dGA==0) dst[px+1] = 255;
|
||||
else dst[px+1] = Math.min(255, dst[px+1]/(255 - src[px+1])) * demultiply;
|
||||
|
||||
if ( src[px+2] == 255 && dBA==0) dst[px+2] = 255;
|
||||
else dst[px+2] = Math.min(255, dst[px+2]/(255 - src[px+2])) * demultiply;
|
||||
break;
|
||||
|
||||
case 'color-burn':
|
||||
case 'burn':
|
||||
if ( src[px ] == 0 && dRA==0) dst[px ] = 0;
|
||||
else dst[px ] = (1 - Math.min(1, (1 - dRA)/sRA)) * demultiply;
|
||||
|
||||
if ( src[px+1] == 0 && dGA==0) dst[px+1] = 0;
|
||||
else dst[px+1] = (1 - Math.min(1, (1 - dGA)/sGA)) * demultiply;
|
||||
|
||||
if ( src[px+2] == 0 && dBA==0) dst[px+2] = 0;
|
||||
else dst[px+2] = (1 - Math.min(1, (1 - dBA)/sBA)) * demultiply;
|
||||
break;
|
||||
|
||||
case 'darken':
|
||||
case 'darker':
|
||||
dst[px ] = (sRA>dRA ? dRA : sRA) * demultiply;
|
||||
dst[px+1] = (sGA>dGA ? dGA : sGA) * demultiply;
|
||||
dst[px+2] = (sBA>dBA ? dBA : sBA) * demultiply;
|
||||
break;
|
||||
|
||||
case 'lighten':
|
||||
case 'lighter':
|
||||
dst[px ] = (sRA<dRA ? dRA : sRA) * demultiply;
|
||||
dst[px+1] = (sGA<dGA ? dGA : sGA) * demultiply;
|
||||
dst[px+2] = (sBA<dBA ? dBA : sBA) * demultiply;
|
||||
break;
|
||||
|
||||
case 'exclusion':
|
||||
dst[px ] = (dRA+sRA - 2*dRA*sRA) * demultiply;
|
||||
dst[px+1] = (dGA+sGA - 2*dGA*sGA) * demultiply;
|
||||
dst[px+2] = (dBA+sBA - 2*dBA*sBA) * demultiply;
|
||||
break;
|
||||
|
||||
// ******* UNSUPPORTED
|
||||
default:
|
||||
dst[px] = dst[px+3] = 255;
|
||||
dst[px+1] = px%8==0 ? 255 : 0;
|
||||
dst[px+2] = px%8==0 ? 0 : 255;
|
||||
}
|
||||
}
|
||||
documentContext.putImageData(dstD, left, top);
|
||||
CanvasProvider.returnCanvas(itemCanvas);
|
||||
}
|
||||
};
|
|
@ -11,28 +11,36 @@ Group = Item.extend({
|
|||
this.clipped = false;
|
||||
},
|
||||
|
||||
draw: function(ctx) {
|
||||
draw: function(ctx, param) {
|
||||
if (!this.visible)
|
||||
return;
|
||||
// If the group has an opacity of less then 1, draw its children on a
|
||||
// temporary canvas, and then draw that canvas onto ctx afterwards
|
||||
// with globalAlpha set.
|
||||
if (this.opacity < 1) {
|
||||
var originalCtx = ctx;
|
||||
var size = this.document.size;
|
||||
var tempCanvas = CanvasProvider.getCanvas(size.width, size.height);
|
||||
ctx = tempCanvas.getContext('2d');
|
||||
}
|
||||
for (var i = 0, l = this.children.length; i < l; i++) {
|
||||
this.children[i].draw(ctx);
|
||||
if (this.clipped & i == 0)
|
||||
ctx.clip();
|
||||
}
|
||||
if (tempCanvas) {
|
||||
originalCtx.globalAlpha = this.opacity;
|
||||
originalCtx.drawImage(tempCanvas, 0, 0);
|
||||
// Return the canvas, so it can be reused
|
||||
CanvasProvider.returnCanvas(tempCanvas);
|
||||
var tempCanvas, originalCtx;
|
||||
if(this.blendMode != 'normal' && !param.ignoreBlendMode) {
|
||||
BlendMode.process(ctx, this, param);
|
||||
} else {
|
||||
param.ignoreBlendMode = false;
|
||||
if (this.opacity < 1) {
|
||||
var originalCtx = ctx;
|
||||
// TODO: use strokeBounds for this, when implemented:
|
||||
tempCanvas = CanvasProvider.getCanvas(this.document.size);
|
||||
ctx = tempCanvas.getContext('2d');
|
||||
}
|
||||
for (var i = 0, l = this.children.length; i < l; i++) {
|
||||
this.children[i].draw(ctx, param);
|
||||
if (this.clipped & i == 0)
|
||||
ctx.clip();
|
||||
}
|
||||
if (tempCanvas) {
|
||||
originalCtx.save();
|
||||
originalCtx.globalAlpha = this.opacity;
|
||||
originalCtx.drawImage(tempCanvas, 0, 0);
|
||||
originalCtx.restore();
|
||||
// Return the canvas, so it can be reused
|
||||
CanvasProvider.returnCanvas(tempCanvas);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
145
src/item/Item.js
145
src/item/Item.js
|
@ -40,6 +40,9 @@ Item = Base.extend({
|
|||
return this.copyTo(this.parent);
|
||||
},
|
||||
|
||||
// TODO: isSelected / setSelected
|
||||
// TODO: isFullySelected / setFullySelected
|
||||
|
||||
/**
|
||||
* Specifies whether the item is locked.
|
||||
*
|
||||
|
@ -87,6 +90,20 @@ Item = Base.extend({
|
|||
|
||||
opacity: 1,
|
||||
|
||||
/**
|
||||
* The blend mode of the item.
|
||||
*
|
||||
* Sample code:
|
||||
* <code>
|
||||
* var circle = new Path.Circle(new Point(50, 50), 10);
|
||||
* print(circle.blendMode); // normal
|
||||
*
|
||||
* // Change the blend mode of the path item:
|
||||
* circle.blendMode = 'multiply';
|
||||
* </code>
|
||||
*/
|
||||
blendMode: 'normal',
|
||||
|
||||
/**
|
||||
* Specifies whether the item is hidden.
|
||||
*
|
||||
|
@ -137,6 +154,18 @@ Item = Base.extend({
|
|||
}
|
||||
},
|
||||
|
||||
// TODO: getIsolated / setIsolated (print specific feature)
|
||||
// TODO: get/setKnockout (print specific feature)
|
||||
// TODO get/setAlphaIsShape
|
||||
// TODO: get/setData
|
||||
|
||||
/**
|
||||
* Reverses the order of this item's children
|
||||
*/
|
||||
reverseChildren: function() {
|
||||
this.children.reverse();
|
||||
},
|
||||
|
||||
/**
|
||||
* The first item contained within this item.
|
||||
*/
|
||||
|
@ -228,6 +257,21 @@ Item = Base.extend({
|
|||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether the item is valid, i.e. it hasn't been removed.
|
||||
*
|
||||
* Sample code:
|
||||
* <code>
|
||||
* var path = new Path();
|
||||
* print(path.isValid()); // true
|
||||
* path.remove();
|
||||
* print(path.isValid()); // false
|
||||
* </code>
|
||||
*
|
||||
* @return {@true if the item is valid}
|
||||
*/
|
||||
// TODO: isValid / checkValid
|
||||
|
||||
/**
|
||||
* {@grouptitle Hierarchy Operations}
|
||||
*
|
||||
|
@ -331,6 +375,40 @@ Item = Base.extend({
|
|||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* {@grouptitle Hierarchy Tests}
|
||||
*
|
||||
* Checks if this item is above the specified item in the stacking order of
|
||||
* the document.
|
||||
*
|
||||
* Sample code:
|
||||
* <code>
|
||||
* var firstPath = new Path();
|
||||
* var secondPath = new Path();
|
||||
* print(secondPath.isAbove(firstPath)); // true
|
||||
* </code>
|
||||
*
|
||||
* @param item The item to check against
|
||||
* @return {@true if it is above the specified item}
|
||||
*/
|
||||
// TODO: isAbove
|
||||
|
||||
/**
|
||||
* Checks if the item is below the specified item in the stacking order of
|
||||
* the document.
|
||||
*
|
||||
* Sample code:
|
||||
* <code>
|
||||
* var firstPath = new Path();
|
||||
* var secondPath = new Path();
|
||||
* print(firstPath.isBelow(secondPath)); // true
|
||||
* </code>
|
||||
*
|
||||
* @param item The item to check against
|
||||
* @return {@true if it is below the specified item}
|
||||
*/
|
||||
// TODO: isBelow
|
||||
|
||||
// TODO: this is confusing the beans
|
||||
// isParent: function(item) {
|
||||
// return this.parent == item;
|
||||
|
@ -355,7 +433,7 @@ Item = Base.extend({
|
|||
* @return {@true if it is inside the specified item}
|
||||
*/
|
||||
isDescendant: function(item) {
|
||||
var parent = this;
|
||||
var parent = this.parent;
|
||||
while(parent) {
|
||||
if (parent == item)
|
||||
return true;
|
||||
|
@ -380,7 +458,7 @@ Item = Base.extend({
|
|||
* @return {@true if the item is an ancestor of the specified item}
|
||||
*/
|
||||
isAncestor: function(item) {
|
||||
var parent = item;
|
||||
var parent = item.parent;
|
||||
while(parent) {
|
||||
if (parent == this)
|
||||
return true;
|
||||
|
@ -388,7 +466,28 @@ Item = Base.extend({
|
|||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the item is grouped with the specified item.
|
||||
*
|
||||
* @param item
|
||||
* @return {@true if the items are grouped together}
|
||||
*/
|
||||
isGroupedWith: function(item) {
|
||||
var parent = this.parent;
|
||||
while(parent) {
|
||||
// Find group parents. Check for parent.parent, since don't want
|
||||
// top level layers, because they also inherit from Group
|
||||
if(parent.parent
|
||||
&& (parent instanceof Group || parent instanceof CompoundPath)
|
||||
&& item.isDescendant(parent))
|
||||
return true;
|
||||
// Keep walking up otherwise
|
||||
parent = parent.parent
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
getBounds: function() {
|
||||
// TODO: Implement for items other than paths
|
||||
return new Rectangle();
|
||||
|
@ -416,7 +515,45 @@ Item = Base.extend({
|
|||
// Now execute the transformation:
|
||||
this.transform(matrix);
|
||||
},
|
||||
|
||||
/**
|
||||
* The bounding rectangle of the item including stroke width.
|
||||
*/
|
||||
// TODO: getStrokeBounds
|
||||
|
||||
/**
|
||||
* The bounding rectangle of the item including stroke width and controls.
|
||||
*/
|
||||
// TODO: getControlBounds
|
||||
|
||||
/**
|
||||
* Rasterizes the item into a newly created Raster object. The item itself
|
||||
* is not removed after rasterization.
|
||||
*
|
||||
* @param resolution the resolution of the raster in dpi {@default 72}
|
||||
* @return the newly created Raster item
|
||||
*/
|
||||
rasterize: function(resolution) {
|
||||
// TODO: why would we want to pass a size to rasterize? Seems to produce
|
||||
// weird results on Scriptographer. Also we can't use antialiasing, since
|
||||
// Canvas doesn't support it yet. Document colorMode is also out of the
|
||||
// question for now.
|
||||
if(!resolution)
|
||||
resolution = 72;
|
||||
// TODO: use strokebounds for this:
|
||||
var bounds = this.bounds;
|
||||
var scale = resolution / 72;
|
||||
var canvas = CanvasProvider.getCanvas(bounds.size.multiply(scale));
|
||||
var context = canvas.getContext('2d');
|
||||
var matrix = new Matrix().scale(scale).translate(-bounds.x, -bounds.y);
|
||||
matrix.applyToContext(context);
|
||||
this.draw(context);
|
||||
var raster = new Raster(canvas);
|
||||
raster.position = this.bounds.center;
|
||||
raster.scale(1 / scale);
|
||||
return raster;
|
||||
},
|
||||
|
||||
/**
|
||||
* The item's position within the art board. This is the
|
||||
* {@link Rectangle#getCenter()} of the {@link Item#getBounds()} rectangle.
|
||||
|
@ -569,4 +706,6 @@ Item = Base.extend({
|
|||
setStyle: function(style) {
|
||||
this._style = new PathStyle(this, style);
|
||||
}
|
||||
|
||||
// TODO: toString
|
||||
});
|
|
@ -19,31 +19,58 @@ PlacedSymbol = Item.extend({
|
|||
} else {
|
||||
this.matrix = new Matrix();
|
||||
}
|
||||
// TODO: this should use strokeBounds:
|
||||
this._bounds = this.symbol.definition.bounds.clone();
|
||||
// TODO: should size be cached here, or on Symbol?
|
||||
this._size = this._bounds.size;
|
||||
},
|
||||
|
||||
transformContent: function(matrix, flags) {
|
||||
var bounds = this.bounds;
|
||||
var coords = [bounds.x, bounds.y,
|
||||
bounds.x + bounds.width, bounds.y + bounds.height];
|
||||
matrix.transform(coords, 0, coords, 0, 2);
|
||||
var width = this._size.width;
|
||||
var height = this._size.height;
|
||||
var x = width * -0.5;
|
||||
var y = height * -0.5;
|
||||
var coords = [
|
||||
x, y,
|
||||
x + width, y,
|
||||
x + width, y + height,
|
||||
x, y + height];
|
||||
this.matrix.preConcatenate(matrix);
|
||||
bounds.x = coords[0];
|
||||
bounds.y = coords[1];
|
||||
bounds.width = coords[2] - coords[0];
|
||||
bounds.height = coords[3] - coords[1];
|
||||
this.matrix.transform(coords, 0, coords, 0, 4);
|
||||
|
||||
var xMin = coords[0], xMax = coords[0];
|
||||
var yMin = coords[1], yMax = coords[1];
|
||||
for(var i = 2; i < 8; i += 2) {
|
||||
var x = coords[i];
|
||||
var y = coords[i + 1];
|
||||
xMin = Math.min(x, xMin);
|
||||
xMax = Math.max(x, xMax);
|
||||
yMin = Math.min(y, yMin);
|
||||
yMax = Math.max(y, yMax);
|
||||
};
|
||||
var bounds = this._bounds;
|
||||
bounds.x = xMin;
|
||||
bounds.y = yMin;
|
||||
bounds.width = xMax - xMin;
|
||||
bounds.height = yMax - yMin;
|
||||
},
|
||||
|
||||
getBounds: function() {
|
||||
return this._bounds;
|
||||
},
|
||||
|
||||
draw: function(ctx) {
|
||||
// TODO: we need to preserve strokewidth, but still transform the fill
|
||||
ctx.save();
|
||||
this.matrix.applyToContext(ctx);
|
||||
this.symbol.definition.draw(ctx);
|
||||
ctx.restore();
|
||||
draw: function(ctx, param) {
|
||||
if(this.blendMode != 'normal' && !param.ignoreBlendMode) {
|
||||
BlendMode.process(ctx, this, param);
|
||||
} else {
|
||||
// TODO: we need to preserve strokewidth, but still transform the fill
|
||||
ctx.save();
|
||||
if(param.ignoreBlendMode !== true)
|
||||
this.matrix.applyToContext(ctx);
|
||||
param.ignoreBlendMode = false;
|
||||
this.symbol.definition.draw(ctx, param);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
// TODO:
|
||||
// embed()
|
||||
|
|
|
@ -3,80 +3,122 @@ Raster = Item.extend({
|
|||
|
||||
// TODO: implement url / type, width, height
|
||||
// TODO: have PlacedSymbol & Raster inherit from a shared class?
|
||||
initialize: function(image) {
|
||||
initialize: function(object) {
|
||||
var width, height;
|
||||
this.base();
|
||||
if (image) {
|
||||
this.image = image;
|
||||
var width = image.width;
|
||||
var height = image.height;
|
||||
this.size = new Size(width, height);
|
||||
this._bounds = new Rectangle(-width / 2, -height / 2, width, height);
|
||||
this.matrix = new Matrix();
|
||||
if (object.getContext) {
|
||||
this.canvas = object;
|
||||
width = this.canvas.width;
|
||||
height = this.canvas.height;
|
||||
} else {
|
||||
this._image = object;
|
||||
// TODO: cross browser compatible?
|
||||
width = object.naturalWidth;
|
||||
height = object.naturalHeight;
|
||||
}
|
||||
this._size = new Size(width, height);
|
||||
this._bounds = new Rectangle(-width / 2, -height / 2, width, height);
|
||||
this.matrix = new Matrix();
|
||||
},
|
||||
|
||||
/**
|
||||
* The size of the raster in pixels.
|
||||
*/
|
||||
getSize: function() {
|
||||
return this._size;
|
||||
},
|
||||
|
||||
// TODO: getSize / setSize
|
||||
setSize: function() {
|
||||
var size = Size.read(arguments);
|
||||
var canvas = CanvasProvider.getCanvas(size);
|
||||
var context = canvas.getContext('2d');
|
||||
context.drawImage(this._canvas ? this._canvas : this._image,
|
||||
0, 0, size.width, size.height);
|
||||
// If we already had a canvas, return it to be reused.
|
||||
if (this._canvas)
|
||||
CanvasProvider.returnCanvas(this._canvas);
|
||||
this._size = size;
|
||||
this._context = null;
|
||||
this._canvas = canvas;
|
||||
},
|
||||
|
||||
/**
|
||||
* The width of the raster in pixels.
|
||||
*/
|
||||
getWidth: function() {
|
||||
return this.size.width;
|
||||
return this._size.width;
|
||||
},
|
||||
|
||||
/**
|
||||
* The height of the raster in pixels.
|
||||
*/
|
||||
getHeight: function() {
|
||||
return this.size.height;
|
||||
return this._size.height;
|
||||
},
|
||||
|
||||
// TODO: getPpi
|
||||
// TODO: getSubImage
|
||||
// TODO: getImage
|
||||
// TODO: drawImage
|
||||
|
||||
// TODO: support getAverageColor paramaters: point, rect, path
|
||||
// TODO: Idea for getAverageColor(path): set globalCompositeOperation = 'xor',
|
||||
// then fillRect with black, then draw the path, then draw the image, then
|
||||
// resize and count values.
|
||||
getAverageColor: function() {
|
||||
var size = 32;
|
||||
var tempCanvas = CanvasProvider.getCanvas(size, size);
|
||||
var ctx = tempCanvas.getContext('2d');
|
||||
ctx.drawImage(this.image, 0, 0, size, size);
|
||||
var pixels = ctx.getImageData(0.5, 0.5, size, size).data;
|
||||
var channels = [0, 0, 0];
|
||||
|
||||
for (var i = 0; i < size; i++) {
|
||||
var offset = i * size;
|
||||
var alpha = pixels[offset + 3] / 255;
|
||||
channels[0] += pixels[offset] * alpha;
|
||||
channels[1] += pixels[offset + 1] * alpha;
|
||||
channels[2] += pixels[offset + 2] * alpha;
|
||||
}
|
||||
|
||||
for (var i = 0; i < 3; i++)
|
||||
channels[i] /= size * 255;
|
||||
|
||||
CanvasProvider.returnCanvas(tempCanvas);
|
||||
return Color.read(channels);
|
||||
/**
|
||||
* Pixels per inch of the raster at it's current size.
|
||||
*/
|
||||
getPpi: function() {
|
||||
var matrix = this.matrix;
|
||||
var orig = new Point(0, 0).transform(matrix);
|
||||
var u = new Point(1, 0).transform(matrix).subtract(orig);
|
||||
var v = new Point(0, 1).transform(matrix).subtract(orig);
|
||||
return new Size(
|
||||
72 / u.length,
|
||||
72 / v.length
|
||||
);
|
||||
},
|
||||
|
||||
// TODO: getPixel(point)
|
||||
// TODO: test this
|
||||
getPixel: function(x, y) {
|
||||
var pixels = this.context.getImageData(x + 0.5, y + 0.5, 1, 1).data;
|
||||
getSubImage: function(/* rectangle */) {
|
||||
var rectangle = Rectangle.read(arguments);
|
||||
var canvas = CanvasProvider.getCanvas(rectangle.size);
|
||||
var context = canvas.getContext('2d');
|
||||
context.drawImage(this.canvas, rectangle.x, rectangle.y,
|
||||
canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
|
||||
return canvas;
|
||||
},
|
||||
|
||||
getImage: function() {
|
||||
return this._image || this.canvas;
|
||||
},
|
||||
|
||||
// TODO: setImage
|
||||
|
||||
// TODO: drawImage(image, point)
|
||||
drawImage: function(image, x, y) {
|
||||
var point = center = Point.read(arguments, 1);
|
||||
this.context.drawImage(image, x, y);
|
||||
},
|
||||
|
||||
/**
|
||||
* {@grouptitle Pixels}
|
||||
*
|
||||
* Gets the color of a pixel in the raster.
|
||||
* @param x
|
||||
* @param y
|
||||
*/
|
||||
getPixel: function() {
|
||||
var point = Point.read(arguments);
|
||||
var ctx = this.context;
|
||||
var pixels = ctx.getImageData(point.x + 0.5, point.y + 0.5, 1, 1).data;
|
||||
var channels = [];
|
||||
for(var i = 0; i < 4; i++)
|
||||
for (var i = 0; i < 4; i++)
|
||||
channels.push(pixels[i] / 255);
|
||||
return Color.read(channels);
|
||||
},
|
||||
|
||||
// TODO: setPixel(point, color)
|
||||
// setPixel: function(x, y, color) {
|
||||
//
|
||||
// }
|
||||
setPixel: function(x, y, color) {
|
||||
color = Color.read(arguments, 2);
|
||||
var ctx = this.context;
|
||||
var imageData = ctx.getImageData(x, y, 1, 1);
|
||||
imageData.data[0] = color.red * 255;
|
||||
imageData.data[1] = color.green * 255;
|
||||
imageData.data[2] = color.blue * 255;
|
||||
imageData.data[3] = color.alpha != -1 ? color.alpha * 255 : 255;
|
||||
ctx.putImageData(imageData, x, y);
|
||||
},
|
||||
|
||||
getContext: function() {
|
||||
if (!this._context)
|
||||
|
@ -90,40 +132,144 @@ Raster = Item.extend({
|
|||
|
||||
getCanvas: function() {
|
||||
if (!this._canvas) {
|
||||
this._canvas = CanvasProvider.getCanvas(this.size.width, this.size.height);
|
||||
this._canvas = CanvasProvider.getCanvas(this.size);
|
||||
this.ctx = this._canvas.getContext('2d');
|
||||
this.ctx.drawImage(this.image, 0, 0);
|
||||
this.ctx.drawImage(this._image, 0, 0);
|
||||
}
|
||||
return this._canvas;
|
||||
},
|
||||
|
||||
setCanvas: function(canvas) {
|
||||
CanvasProvider.returnCanvas(this._canvas);
|
||||
if (this._canvas)
|
||||
CanvasProvider.returnCanvas(this._canvas);
|
||||
// TODO: should the width / height of the bounds be reset too?
|
||||
this._size = new Size(canvas.width, canvas.height);
|
||||
this._image = null;
|
||||
this._ctx = null;
|
||||
this._canvas = canvas;
|
||||
},
|
||||
|
||||
transformContent: function(matrix, flags) {
|
||||
var bounds = this.bounds;
|
||||
var coords = [bounds.x, bounds.y,
|
||||
bounds.x + bounds.width, bounds.y + bounds.height];
|
||||
matrix.transform(coords, 0, coords, 0, 2);
|
||||
var width = this._size.width;
|
||||
var height = this._size.height;
|
||||
var x = width * -0.5;
|
||||
var y = height * -0.5;
|
||||
var coords = [
|
||||
x, y,
|
||||
x + width, y,
|
||||
x + width, y + height,
|
||||
x, y + height];
|
||||
this.matrix.preConcatenate(matrix);
|
||||
bounds.x = coords[0];
|
||||
bounds.y = coords[1];
|
||||
bounds.width = coords[2] - coords[0];
|
||||
bounds.height = coords[3] - coords[1];
|
||||
this.matrix.transform(coords, 0, coords, 0, 4);
|
||||
|
||||
var xMin = coords[0], xMax = coords[0];
|
||||
var yMin = coords[1], yMax = coords[1];
|
||||
for(var i = 2; i < 8; i += 2) {
|
||||
var x = coords[i];
|
||||
var y = coords[i + 1];
|
||||
xMin = Math.min(x, xMin);
|
||||
xMax = Math.max(x, xMax);
|
||||
yMin = Math.min(y, yMin);
|
||||
yMax = Math.max(y, yMax);
|
||||
};
|
||||
var bounds = this._bounds;
|
||||
bounds.x = xMin;
|
||||
bounds.y = yMin;
|
||||
bounds.width = xMax - xMin;
|
||||
bounds.height = yMax - yMin;
|
||||
},
|
||||
|
||||
getBounds: function() {
|
||||
return this._bounds;
|
||||
},
|
||||
|
||||
draw: function(ctx) {
|
||||
ctx.save();
|
||||
this.matrix.applyToContext(ctx);
|
||||
ctx.drawImage(this._canvas || this.image,
|
||||
-this.size.width / 2, -this.size.height / 2);
|
||||
ctx.restore();
|
||||
draw: function(ctx, param) {
|
||||
if(this.blendMode != 'normal' && !param.ignoreBlendMode) {
|
||||
BlendMode.process(ctx, this, param);
|
||||
} else {
|
||||
ctx.save();
|
||||
if(param.ignoreBlendMode !== true)
|
||||
this.matrix.applyToContext(ctx);
|
||||
ctx.drawImage(this._canvas || this._image,
|
||||
-this.size.width / 2, -this.size.height / 2);
|
||||
ctx.restore();
|
||||
param.ignoreBlendMode = false;
|
||||
}
|
||||
}
|
||||
}, new function() {
|
||||
function getAverageColor(pixels) {
|
||||
var channels = [0, 0, 0];
|
||||
var total = 0;
|
||||
for (var i = 0, l = pixels.length / 4; i < l; i++) {
|
||||
var offset = i * 4;
|
||||
var alpha = pixels[offset + 3] / 255;
|
||||
total += alpha;
|
||||
channels[0] += pixels[offset] * alpha;
|
||||
channels[1] += pixels[offset + 1] * alpha;
|
||||
channels[2] += pixels[offset + 2] * alpha;
|
||||
}
|
||||
for (var i = 0; i < 3; i++)
|
||||
channels[i] /= total * 255;
|
||||
return total ? Color.read(channels) : null;
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* {@grouptitle Average Color}
|
||||
* Calculates the average color of the image within the given path,
|
||||
* rectangle or point. This can be used for creating raster image
|
||||
* effects.
|
||||
*
|
||||
* @param object
|
||||
* @return the average color contained in the area covered by the
|
||||
* specified path, rectangle or point.
|
||||
*/
|
||||
getAverageColor: function(object) {
|
||||
var image;
|
||||
if (object) {
|
||||
var bounds, path;
|
||||
if (object instanceof Path) {
|
||||
// TODO: what if the path is smaller than 1 px?
|
||||
// TODO: how about rounding of bounds.size?
|
||||
// TODO: test with compound paths.
|
||||
path = object;
|
||||
bounds = object.bounds;
|
||||
} else if (object.width) {
|
||||
bounds = new Rectangle(object);
|
||||
} else if (object.x) {
|
||||
bounds = new Rectangle(object.x - 0.5, object.y - 0.5, 1, 1);
|
||||
}
|
||||
|
||||
var canvas = CanvasProvider.getCanvas(bounds.size);
|
||||
var ctx = canvas.getContext('2d');
|
||||
var delta = bounds.topLeft.multiply(-1);
|
||||
ctx.translate(delta.x, delta.y);
|
||||
if (path) {
|
||||
var style = object.style;
|
||||
path.draw(ctx);
|
||||
ctx.clip();
|
||||
path.style = style;
|
||||
}
|
||||
var matrix = this.matrix.clone();
|
||||
var transMatrix = Matrix.getTranslateInstance(delta);
|
||||
matrix.preConcatenate(transMatrix);
|
||||
matrix.applyToContext(ctx);
|
||||
ctx.drawImage(this._canvas || this._image,
|
||||
-this.size.width / 2, -this.size.height / 2);
|
||||
image = canvas;
|
||||
} else {
|
||||
image = this.image;
|
||||
}
|
||||
var size = new Size(32);
|
||||
var sampleCanvas = CanvasProvider.getCanvas(size);
|
||||
var ctx = sampleCanvas.getContext('2d');
|
||||
ctx.drawImage(image, 0, 0, size.width, size.height);
|
||||
var pixels = ctx.getImageData(0.5, 0.5, size.width, size.height).data;
|
||||
var color = getAverageColor(pixels);
|
||||
CanvasProvider.returnCanvas(sampleCanvas);
|
||||
if (image instanceof HTMLCanvasElement)
|
||||
CanvasProvider.returnCanvas(image);
|
||||
return color;
|
||||
}
|
||||
}
|
||||
});
|
|
@ -19,29 +19,53 @@ CompoundPath = PathItem.extend(new function() {
|
|||
}
|
||||
},
|
||||
|
||||
draw: function(ctx) {
|
||||
draw: function(ctx, param) {
|
||||
if(!this.visible)
|
||||
return;
|
||||
if (this.children.length) {
|
||||
var firstChild = this.children[0];
|
||||
ctx.beginPath();
|
||||
for (var i = 0, l = this.children.length; i < l; i++) {
|
||||
var child = this.children[i];
|
||||
child.draw(ctx, true);
|
||||
}
|
||||
firstChild.setCtxStyles(ctx);
|
||||
if (firstChild.fillColor) {
|
||||
ctx.fillStyle = firstChild.fillColor.getCssString();
|
||||
ctx.fill();
|
||||
}
|
||||
if (firstChild.strokeColor) {
|
||||
ctx.strokeStyle = firstChild.strokeColor.getCssString();
|
||||
ctx.stroke();
|
||||
if(this.blendMode != 'normal' && !param.ignoreBlendMode) {
|
||||
BlendMode.process(ctx, this, param);
|
||||
} else {
|
||||
var firstChild = this.children[0];
|
||||
ctx.beginPath();
|
||||
param.compound = true;
|
||||
for (var i = 0, l = this.children.length; i < l; i++) {
|
||||
var child = this.children[i];
|
||||
child.draw(ctx, param);
|
||||
}
|
||||
param.compound = false;
|
||||
firstChild.setCtxStyles(ctx);
|
||||
if (firstChild.fillColor) {
|
||||
ctx.fillStyle = firstChild.fillColor.getCssString();
|
||||
ctx.fill();
|
||||
}
|
||||
if (firstChild.strokeColor) {
|
||||
ctx.strokeStyle = firstChild.strokeColor.getCssString();
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// TODO: add getBounds
|
||||
// TODO: have getBounds of Group / Layer / CompoundPath use the same
|
||||
// code (from a utility script?)
|
||||
getBounds: function() {
|
||||
if (this.children.length) {
|
||||
var rect = this.children[0].bounds;
|
||||
var x1 = rect.x;
|
||||
var y1 = rect.y;
|
||||
var x2 = rect.x + rect.width;
|
||||
var y2 = rect.y + rect.height;
|
||||
for (var i = 1, l = this.children.length; i < l; i++) {
|
||||
var rect2 = this.children[i].bounds;
|
||||
x1 = Math.min(rect2.x, x1);
|
||||
y1 = Math.min(rect2.y, y1);
|
||||
x2 = Math.max(rect2.x + rect2.width, x1 + x2 - x1);
|
||||
y2 = Math.max(rect2.y + rect2.height, y1 + y2 - y1);
|
||||
}
|
||||
}
|
||||
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
|
||||
},
|
||||
|
||||
/**
|
||||
* If this is a compound path with only one path inside,
|
||||
|
@ -73,17 +97,10 @@ CompoundPath = PathItem.extend(new function() {
|
|||
},
|
||||
|
||||
moveBy: function() {
|
||||
if (!arguments.length) {
|
||||
// TODO: Shouldn't this be relative to the previous position
|
||||
// in lack of an argument? This should then be corrected in
|
||||
// Scriptographer too.
|
||||
this.moveTo(0, 0);
|
||||
} else {
|
||||
var point = Point.read(arguments);
|
||||
var path = getCurrentPath(this);
|
||||
var current = path.segments[path.segments.length - 1].point;
|
||||
this.moveTo(current.add(point));
|
||||
}
|
||||
var point = arguments.length ? Point.read(arguments) : new Point();
|
||||
var path = getCurrentPath(this);
|
||||
var current = path.segments[path.segments.length - 1].point;
|
||||
this.moveTo(current.add(point));
|
||||
},
|
||||
|
||||
closePath: function() {
|
||||
|
|
|
@ -228,7 +228,7 @@ Path = PathItem.extend({
|
|||
|
||||
lineTo: function() {
|
||||
var segment = Segment.read(arguments);
|
||||
if (segment && this._segments.length)
|
||||
if (segment)
|
||||
this.addSegment(segment);
|
||||
},
|
||||
|
||||
|
@ -403,57 +403,63 @@ Path = PathItem.extend({
|
|||
this.closed = ture;
|
||||
},
|
||||
|
||||
draw: function(ctx, compound) {
|
||||
draw: function(ctx, param) {
|
||||
if (!this.visible) return;
|
||||
if (!compound)
|
||||
ctx.beginPath();
|
||||
|
||||
var segments = this._segments;
|
||||
var length = segments.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
var segment = segments[i];
|
||||
var x = segment.point.x;
|
||||
var y = segment.point.y;
|
||||
var handleIn = segment.handleIn;
|
||||
if (i == 0) {
|
||||
ctx.moveTo(x, y);
|
||||
} else {
|
||||
if (handleOut.isZero() && handleIn.isZero()) {
|
||||
ctx.lineTo(x, y);
|
||||
if(this.blendMode != 'normal' && !param.ignoreBlendMode) {
|
||||
BlendMode.process(ctx, this, param);
|
||||
} else {
|
||||
param.ignoreBlendMode = false;
|
||||
if (!param.compound)
|
||||
ctx.beginPath();
|
||||
|
||||
var segments = this._segments;
|
||||
var length = segments.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
var segment = segments[i];
|
||||
var x = segment.point.x;
|
||||
var y = segment.point.y;
|
||||
var handleIn = segment.handleIn;
|
||||
if (i == 0) {
|
||||
ctx.moveTo(x, y);
|
||||
} else {
|
||||
ctx.bezierCurveTo(
|
||||
outX, outY,
|
||||
handleIn.x + x, handleIn.y + y,
|
||||
x, y
|
||||
);
|
||||
if (handleOut.isZero() && handleIn.isZero()) {
|
||||
ctx.lineTo(x, y);
|
||||
} else {
|
||||
ctx.bezierCurveTo(
|
||||
outX, outY,
|
||||
handleIn.x + x, handleIn.y + y,
|
||||
x, y
|
||||
);
|
||||
}
|
||||
}
|
||||
var handleOut = segment.handleOut;
|
||||
var outX = handleOut.x + x;
|
||||
var outY = handleOut.y + y;
|
||||
}
|
||||
var handleOut = segment.handleOut;
|
||||
var outX = handleOut.x + x;
|
||||
var outY = handleOut.y + y;
|
||||
}
|
||||
if (this.closed && length > 1) {
|
||||
var segment = segments[0];
|
||||
var x = segment.point.x;
|
||||
var y = segment.point.y;
|
||||
var handleIn = segment.handleIn;
|
||||
ctx.bezierCurveTo(outX, outY, handleIn.x + x, handleIn.y + y, x, y);
|
||||
ctx.closePath();
|
||||
}
|
||||
if (!compound) {
|
||||
this.setCtxStyles(ctx);
|
||||
ctx.save();
|
||||
ctx.globalAlpha = this.opacity;
|
||||
if (this.fillColor) {
|
||||
ctx.fillStyle = this.fillColor.getCanvasStyle(ctx);
|
||||
ctx.fill();
|
||||
if (this.closed && length > 1) {
|
||||
var segment = segments[0];
|
||||
var x = segment.point.x;
|
||||
var y = segment.point.y;
|
||||
var handleIn = segment.handleIn;
|
||||
ctx.bezierCurveTo(outX, outY, handleIn.x + x, handleIn.y + y, x, y);
|
||||
ctx.closePath();
|
||||
}
|
||||
if (this.strokeColor) {
|
||||
ctx.strokeStyle = this.strokeColor.getCanvasStyle(ctx);
|
||||
ctx.stroke();
|
||||
if (!param.compound) {
|
||||
this.setCtxStyles(ctx);
|
||||
ctx.save();
|
||||
ctx.globalAlpha = this.opacity;
|
||||
if (this.fillColor) {
|
||||
ctx.fillStyle = this.fillColor.getCanvasStyle(ctx);
|
||||
ctx.fill();
|
||||
}
|
||||
if (this.strokeColor) {
|
||||
ctx.strokeStyle = this.strokeColor.getCanvasStyle(ctx);
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
}
|
||||
}, new function() { // inject methods that require scoped privates
|
||||
/**
|
||||
|
|
|
@ -34,15 +34,6 @@ Segment = Base.extend({
|
|||
this.handleOut = new Point();
|
||||
},
|
||||
|
||||
// TODO:
|
||||
// insert: function() {
|
||||
// if (this._segments && this._segments.path) {
|
||||
// var path = this._segments.path;
|
||||
// path.checkValid();
|
||||
//
|
||||
// }
|
||||
// },
|
||||
|
||||
getPoint: function() {
|
||||
return this.point;
|
||||
},
|
||||
|
|
|
@ -12,6 +12,7 @@ Tool = ToolHandler.extend({
|
|||
$(this._document.canvas).removeEvents();
|
||||
this._document = doc || Paper.document;
|
||||
var that = this, curPoint;
|
||||
var dragging = false;
|
||||
var events = {
|
||||
dragstart: function(e) {
|
||||
curPoint = new Point(e.offset);
|
||||
|
@ -20,6 +21,7 @@ Tool = ToolHandler.extend({
|
|||
that._document.redraw();
|
||||
if (that.eventInterval != -1)
|
||||
this.intervalId = setInterval(events.drag, that.eventInterval);
|
||||
dragging = true;
|
||||
},
|
||||
drag: function(e) {
|
||||
if (e) curPoint = new Point(e.offset);
|
||||
|
@ -36,12 +38,15 @@ Tool = ToolHandler.extend({
|
|||
that.onHandleEvent('MOUSE_UP', new Point(e.offset), null, null);
|
||||
if (that.onMouseUp)
|
||||
that._document.redraw();
|
||||
dragging = false;
|
||||
},
|
||||
mousemove: function(e) {
|
||||
if(!dragging) {
|
||||
that.onHandleEvent('MOUSE_MOVE', new Point(e.offset), null, null);
|
||||
if (that.onMouseMove)
|
||||
that._document.redraw();
|
||||
}
|
||||
}
|
||||
// TODO: This is currently interfering with the drag code, needs fixing:
|
||||
// mousemove: function(e) {
|
||||
// that.onHandleEvent('MOUSE_MOVE', new Point(e.offset), null, null);
|
||||
// that._document.redraw();
|
||||
// }
|
||||
};
|
||||
$(doc.canvas).addEvents(events);
|
||||
},
|
||||
|
|
|
@ -1,15 +1,32 @@
|
|||
// TODO: it might be better to make a ContextProvider class, since you
|
||||
// can always find the canvas through context.canvas. This saves code and
|
||||
// speed by not having to do canvas.getContext('2d')
|
||||
// TODO: Run through the canvas array to find a canvas with the requested
|
||||
// width / height, so we don't need to resize it?
|
||||
CanvasProvider = {
|
||||
canvases: [],
|
||||
getCanvas: function(width, height) {
|
||||
var canvas = this.canvases.length
|
||||
? this.canvases.pop()
|
||||
: document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
return canvas;
|
||||
getCanvas: function(size) {
|
||||
if(this.canvases.length) {
|
||||
var canvas = this.canvases.pop();
|
||||
// If they are not the same size, we don't need to clear them
|
||||
// using clearRect and visa versa.
|
||||
if((canvas.width != size.width) || (canvas.height != size.height)) {
|
||||
canvas.width = size.width;
|
||||
canvas.height = size.height;
|
||||
} else {
|
||||
var context = canvas.getContext('2d');
|
||||
context.clearRect(0, 0, size.width + 1, size.height + 1);
|
||||
}
|
||||
return canvas;
|
||||
} else {
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = size.width;
|
||||
canvas.height = size.height;
|
||||
return canvas;
|
||||
}
|
||||
},
|
||||
|
||||
returnCanvas: function(canvas) {
|
||||
this.canvases.push(canvas);
|
||||
}
|
||||
};
|
||||
};
|
|
@ -14,9 +14,11 @@
|
|||
<script type="text/javascript" src="../src/basic/Size.js"></script>
|
||||
<script type="text/javascript" src="../src/basic/Matrix.js"></script>
|
||||
<script type="text/javascript" src="../src/document/Doc.js"></script>
|
||||
<script type="text/javascript" src="../src/document/Symbol.js"></script>
|
||||
<script type="text/javascript" src="../src/item/Item.js"></script>
|
||||
<script type="text/javascript" src="../src/item/Group.js"></script>
|
||||
<script type="text/javascript" src="../src/item/Layer.js"></script>
|
||||
<script type="text/javascript" src="../src/item/PlacedSymbol.js"></script>
|
||||
<script type="text/javascript" src="../src/item/PathStyle.js"></script>
|
||||
<script type="text/javascript" src="../src/path/Segment.js"></script>
|
||||
<script type="text/javascript" src="../src/path/PathItem.js"></script>
|
||||
|
@ -48,6 +50,7 @@
|
|||
<script type="text/javascript" src="tests/Path_Bounds.js"></script>
|
||||
<script type="text/javascript" src="tests/Path_Length.js"></script>
|
||||
<script type="text/javascript" src="tests/PathStyle.js"></script>
|
||||
<script type="text/javascript" src="tests/PlacedSymbol.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="qunit-header">QUnit Test Suite</h1>
|
||||
|
|
|
@ -68,5 +68,11 @@ test('Converting Colors', function() {
|
|||
|
||||
var color = new GrayColor(0.2);
|
||||
var rgbColor = new RGBColor(color);
|
||||
compareRGBColors(rgbColor, [ 0.8, 0.8, 0.8, 1 ]);
|
||||
compareRGBColors(rgbColor, [ 0.8, 0.8, 0.8, 1]);
|
||||
});
|
||||
|
||||
test('Setting RGBColor#gray', function() {
|
||||
var color = new RGBColor(1, 0.5, 0.2);
|
||||
color.gray = 0.1;
|
||||
compareRGBColors(color, [ 0.9, 0.9, 0.9, 1]);
|
||||
});
|
|
@ -1,6 +1,7 @@
|
|||
module('Path');
|
||||
|
||||
test('path.currentSegment', function() {
|
||||
var doc = new Doc();
|
||||
var path = new Path();
|
||||
path.moveTo([50, 50]);
|
||||
path.lineTo([100, 100]);
|
||||
|
|
|
@ -28,4 +28,8 @@ test('path.bounds', function() {
|
|||
// Set new bounds and check segment list as result of resizing / positioning
|
||||
path.bounds = { x: 100, y: 100, width: 200, height: 200 };
|
||||
compareSegmentLists(path.segments, [{ point: { x: 107.93066, y: 179.56982 }, handleIn: { x: -24.41211, y: 51.30664 }, handleOut: { x: 39.52734, y: -83.08447 } }, { point: { x: 271.10107, y: 160.66553 }, handleIn: { x: -53.96289, y: -99.9126 }, handleOut: { x: 53.96143, y: 99.91406 } }, { point: { x: 215.85303, y: 296.96045 }, handleIn: { x: 85.81299, y: -17.18555 }, handleOut: { x: -101.49854, y: 20.32861 } }])
|
||||
});
|
||||
|
||||
path.rotate(40);
|
||||
compareRectangles(path.bounds, { x: 92.38155, y: 106.78981, width: 191.48048, height: 203.66789 });
|
||||
compareSegmentLists(path.segments, [{ point: { x: 142.604, y: 125.16748 }, handleIn: { x: -51.6792, y: 23.61182 }, handleOut: { x: 83.68457, y: -38.23438 } }, { point: { x: 279.75, y: 215.57129 }, handleIn: { x: 22.88525, y: -111.22363 }, handleOut: { x: -22.88623, y: 111.22363 } }, { point: { x: 149.81982, y: 284.46729 }, handleIn: { x: 76.78223, y: 41.99219 }, handleOut: { x: -90.81885, y: -49.67139 } }]);
|
||||
});
|
24
test/tests/PlacedSymbol.js
Normal file
24
test/tests/PlacedSymbol.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
module('Placed Symbol');
|
||||
|
||||
test('placedSymbol bounds', function() {
|
||||
var doc = new Doc();
|
||||
var path = new Path.Circle([50, 50], 50);
|
||||
var symbol = new Symbol(path);
|
||||
var placedSymbol = new PlacedSymbol(symbol);
|
||||
|
||||
// These tests currently fail because we haven't implemented
|
||||
// Item#strokeBounds yet.
|
||||
compareRectangles(placedSymbol.bounds,
|
||||
new Rectangle(-50.5, -50.5, 101, 101),
|
||||
'PlacedSymbol initial bounds.');
|
||||
|
||||
placedSymbol.scale(0.5);
|
||||
compareRectangles(placedSymbol.bounds,
|
||||
{ x: -25.5, y: -25.5, width: 51, height: 51 },
|
||||
'Bounds after scale');
|
||||
|
||||
placedSymbol.rotate(40);
|
||||
compareRectangles(placedSymbol.bounds,
|
||||
{ x: -25.50049, y: -25.50049, width: 51.00098, height: 51.00098 },
|
||||
'Bounds after rotation');
|
||||
});
|
|
@ -89,6 +89,33 @@ test('isDescendant(item) / isAncestor(item)', function() {
|
|||
|
||||
equals(path.isAncestor(doc.activeLayer), false);
|
||||
equals(doc.activeLayer.isAncestor(path), true);
|
||||
|
||||
// an item can't be its own descendant:
|
||||
equals(doc.activeLayer.isDescendant(doc.activeLayer), false);
|
||||
|
||||
// an item can't be its own ancestor:
|
||||
equals(doc.activeLayer.isAncestor(doc.activeLayer), false);
|
||||
});
|
||||
|
||||
test('isGroupedWith', function() {
|
||||
var doc = new Doc();
|
||||
var path = new Path();
|
||||
var secondPath = new Path();
|
||||
var group = new Group([path]);
|
||||
var secondGroup = new Group([secondPath]);
|
||||
|
||||
equals(path.isGroupedWith(secondPath), false);
|
||||
secondGroup.appendTop(path);
|
||||
equals(path.isGroupedWith(secondPath), true);
|
||||
equals(path.isGroupedWith(group), false);
|
||||
equals(path.isDescendant(secondGroup), true);
|
||||
equals(secondGroup.isDescendant(path), false);
|
||||
equals(secondGroup.isDescendant(secondGroup), false);
|
||||
equals(path.isGroupedWith(secondGroup), false);
|
||||
Paper.document.activeLayer.appendTop(path);
|
||||
equals(path.isGroupedWith(secondPath), false);
|
||||
Paper.document.activeLayer.appendTop(secondPath);
|
||||
equals(path.isGroupedWith(secondPath), false);
|
||||
});
|
||||
|
||||
test('getPreviousSibling() / getNextSibling()', function() {
|
||||
|
@ -105,4 +132,16 @@ test('hidden', function() {
|
|||
var firstPath = new Path();
|
||||
firstPath.visible = false;
|
||||
equals(firstPath.hidden, true);
|
||||
});
|
||||
});
|
||||
|
||||
test('reverseChildren()', function() {
|
||||
var doc = new Doc();
|
||||
var path = new Path();
|
||||
var secondPath = new Path();
|
||||
var thirdPath = new Path();
|
||||
equals(doc.activeLayer.firstChild == path, true);
|
||||
doc.activeLayer.reverseChildren();
|
||||
equals(doc.activeLayer.firstChild == path, false);
|
||||
equals(doc.activeLayer.firstChild == thirdPath, true);
|
||||
equals(doc.activeLayer.lastChild == path, true);
|
||||
})
|
Loading…
Reference in a new issue