Merge remote-tracking branch 'origin/master'

This commit is contained in:
Jürg Lehni 2011-02-26 17:27:12 +01:00
commit 506e7c036c
19 changed files with 1095 additions and 213 deletions

View file

@ -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());
}

View file

@ -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;
},
/**

View file

@ -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
View 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);
}
};

View file

@ -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);
}
}
},

View file

@ -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
});

View file

@ -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()

View file

@ -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;
}
}
});

View file

@ -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() {

View file

@ -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
/**

View file

@ -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;
},

View file

@ -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);
},

View file

@ -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);
}
};
};

View file

@ -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>

View file

@ -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]);
});

View file

@ -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]);

View file

@ -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 } }]);
});

View 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');
});

View file

@ -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);
})