Fix how gradient matrices are handled when Item#applyMatrix = false

Closes #1238
This commit is contained in:
Jürg Lehni 2017-03-09 14:31:41 +01:00
parent 920cbaca99
commit 5291043a5f
6 changed files with 92 additions and 41 deletions

View file

@ -3358,18 +3358,19 @@ new function() { // Injection scope for hit-test functions shared with project
if (matrix && matrix.isIdentity())
matrix = null;
var _matrix = this._matrix,
transform = matrix && !matrix.isIdentity(),
applyMatrix = (_applyMatrix || this._applyMatrix)
// Don't apply _matrix if the result of concatenating with
// matrix would be identity.
&& ((!_matrix.isIdentity() || matrix)
&& ((!_matrix.isIdentity() || transform)
// Even if it's an identity matrix, we still need to
// recursively apply the matrix to children.
|| _applyMatrix && _applyRecursively && this._children);
// Bail out if there is nothing to do.
if (!matrix && !applyMatrix)
if (!transform && !applyMatrix)
return this;
// Simply prepend the internal matrix with the passed one:
if (matrix) {
if (transform) {
// Keep a backup of the last valid state before the matrix becomes
// non-invertible. This is then used again in setBounds to restore.
if (!matrix.isInvertible() && _matrix.isInvertible())
@ -3380,28 +3381,39 @@ new function() { // Injection scope for hit-test functions shared with project
// internal _matrix transformations to the item's content.
// Application is not possible on Raster, PointText, SymbolItem, since
// the matrix is where the actual transformation state is stored.
if (applyMatrix = applyMatrix && this._transformContent(_matrix,
_applyRecursively, _setApplyMatrix)) {
// When the _matrix could be applied, we also need to transform
// color styles (only gradients so far) and pivot point:
var pivot = this._pivot,
style = this._style,
// pass true for _dontMerge so we don't recursively transform
if (applyMatrix) {
if (this._transformContent(_matrix, _applyRecursively,
_setApplyMatrix)) {
var pivot = this._pivot;
if (pivot)
_matrix._transformPoint(pivot, pivot, true);
// Reset the internal matrix to the identity transformation if
// it was possible to apply it.
_matrix.reset(true);
// Set the internal _applyMatrix flag to true if we're told to
// do so
if (_setApplyMatrix && this._canApplyMatrix)
this._applyMatrix = true;
} else {
applyMatrix = transform = false;
}
}
if (transform) {
// When a new matrix was applied, we also need to transform gradient
// color points. These always need transforming, regardless of
// #applyMatrix, as they are defined in the parent's coordinate
// system.
// TODO: Introduce options to control whether fills should be
// transformed or not.
var style = this._style,
// Pass true for _dontMerge so we don't recursively transform
// styles on groups' children.
fillColor = style.getFillColor(true),
strokeColor = style.getStrokeColor(true);
if (pivot)
_matrix._transformPoint(pivot, pivot, true);
if (fillColor)
fillColor.transform(_matrix);
fillColor.transform(matrix);
if (strokeColor)
strokeColor.transform(_matrix);
// Reset the internal matrix to the identity transformation if it
// was possible to apply it.
_matrix.reset(true);
// Set the internal _applyMatrix flag to true if we're told to do so
if (_setApplyMatrix && this._canApplyMatrix)
this._applyMatrix = true;
strokeColor.transform(matrix);
}
// Calling _changed will clear _bounds and _position, but depending
// on matrix we can calculate and set them again, so preserve them.
@ -4094,12 +4106,13 @@ new function() { // Injection scope for hit-test functions shared with project
_setStyles: function(ctx, param, viewMatrix) {
// We can access internal properties since we're only using this on
// items without children, where styles would be merged.
var style = this._style;
var style = this._style,
matrix = this._matrix;
if (style.hasFill()) {
ctx.fillStyle = style.getFillColor().toCanvasStyle(ctx);
ctx.fillStyle = style.getFillColor().toCanvasStyle(ctx, matrix);
}
if (style.hasStroke()) {
ctx.strokeStyle = style.getStrokeColor().toCanvasStyle(ctx);
ctx.strokeStyle = style.getStrokeColor().toCanvasStyle(ctx, matrix);
ctx.lineWidth = style.getStrokeWidth();
var strokeJoin = style.getStrokeJoin(),
strokeCap = style.getStrokeCap(),
@ -4245,8 +4258,8 @@ new function() { // Injection scope for hit-test functions shared with project
// on the temporary canvas.
ctx.translate(-itemOffset.x, -itemOffset.y);
}
// Apply globalMatrix when drawing into temporary canvas.
if (transform) {
// Apply viewMatrix when drawing into temporary canvas.
(direct ? matrix : viewMatrix).applyToContext(ctx);
}
if (clip) {

View file

@ -28,8 +28,8 @@ var Shape = Item.extend(/** @lends Shape# */{
radius: null
},
initialize: function Shape(props) {
this._initialize(props);
initialize: function Shape(props, point) {
this._initialize(props, point);
},
_equals: function(item) {
@ -386,11 +386,11 @@ new function() { // Scope for _contains() and _hitTestSelf() code.
// Mess with indentation in order to get more line-space below:
statics: new function() {
function createShape(type, point, size, radius, args) {
var item = new Shape(Base.getNamed(args));
var item = new Shape(Base.getNamed(args), point);
item._type = type;
item._size = size;
item._radius = radius;
return item.translate(point);
return item;
}
return /** @lends Shape */{

View file

@ -834,7 +834,7 @@ var Color = Base.extend(new function() {
+ components.join(',') + ')';
},
toCanvasStyle: function(ctx) {
toCanvasStyle: function(ctx, matrix) {
if (this._canvasStyle)
return this._canvasStyle;
// Normal colors are simply represented by their CSS string.
@ -846,10 +846,20 @@ var Color = Base.extend(new function() {
stops = gradient._stops,
origin = components[1],
destination = components[2],
highlight = components[3],
inverse = matrix && matrix.inverted(),
canvasGradient;
// If the item's content is transformed by a matrix, we need to
// inverse transform the gradient points, as they are defined in
// item's parent coordinate system.
if (inverse) {
origin = inverse._transformPoint(origin);
destination = inverse._transformPoint(destination);
if (highlight)
highlight = inverse._transformPoint(highlight);
}
if (gradient._radial) {
var radius = destination.getDistance(origin),
highlight = components[3];
var radius = destination.getDistance(origin);
if (highlight) {
var vector = highlight.subtract(origin);
if (vector.getLength() > radius)

View file

@ -69,9 +69,9 @@ var GradientStop = Base.extend(/** @lends GradientStop# */{
* Called by various setters whenever a value changes
*/
_changed: function() {
// Loop through the gradients that use this stop and notify them about
// the change, so they can notify their gradient colors, which in turn
// will notify the items they are used in:
// Notify the graident that uses this stop about the change, so it can
// notify its gradient colors, which in turn will notify the items they
// are used in:
if (this._owner)
this._owner._changed(/*#=*/Change.STYLE);
},

View file

@ -389,13 +389,6 @@ new function() {
.translate(bounds.getPoint())
.scale(bounds.getSize()));
}
if (item instanceof Shape) {
// When applying gradient colors to shapes, we need
// to offset the shape's initial position to get the
// same results as SVG.
color.transform(new Matrix().translate(
item.getPosition(true).negate()));
}
}
}
}

View file

@ -248,3 +248,38 @@ test('Gradient', function() {
equals(function() { return stop3.offset; }, 1);
equals(function() { return stop4.offset; }, 0.5);
});
test('Gradients with applyMatrix', function() {
var topLeft = [100, 100];
var bottomRight = [400, 400];
var gradientColor = {
gradient: {
stops: ['yellow', 'red', 'blue']
},
origin: topLeft,
destination: bottomRight
}
var path = new Path.Rectangle({
topLeft: topLeft,
bottomRight: bottomRight,
fillColor: gradientColor,
applyMatrix: true
});
var shape = new Shape.Rectangle({
topLeft: topLeft,
bottomRight: bottomRight,
fillColor: gradientColor,
applyMatrix: false
});
comparePixels(path, shape);
path.scale(2);
path.rotate(45);
shape.scale(2);
shape.rotate(45);
comparePixels(path, shape);
});