Skip to content

Commit

Permalink
Image meet or slice with cropX and cropY value (#4055)
Browse files Browse the repository at this point in the history
* working example
* fixed tests
  • Loading branch information
asturur authored Jul 3, 2017
1 parent 192672a commit c41ef91
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 98 deletions.
3 changes: 3 additions & 0 deletions src/elements_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ fabric.ElementsParser.prototype.createCallback = function(index, el) {
_this.resolveGradient(obj, 'fill');
_this.resolveGradient(obj, 'stroke');
obj._removeTransformMatrix();
if (obj instanceof fabric.Image) {
obj.parsePreserveAspectRatioAttribute(el);
}
_this.reviver && _this.reviver(el, obj);
_this.instances[index] = obj;
_this.checkIfDone();
Expand Down
163 changes: 73 additions & 90 deletions src/shapes/image.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@

var stateProperties = fabric.Object.prototype.stateProperties.concat();
stateProperties.push(
'alignX',
'alignY',
'meetOrSlice'
'cropX',
'cropY'
);

/**
Expand All @@ -44,33 +43,6 @@
*/
crossOrigin: '',

/**
* AlignX value, part of preserveAspectRatio (one of "none", "mid", "min", "max")
* @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
* This parameter defines how the picture is aligned to its viewport when image element width differs from image width.
* @type String
* @default
*/
alignX: 'none',

/**
* AlignY value, part of preserveAspectRatio (one of "none", "mid", "min", "max")
* @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
* This parameter defines how the picture is aligned to its viewport when image element height differs from image height.
* @type String
* @default
*/
alignY: 'none',

/**
* meetOrSlice value, part of preserveAspectRatio (one of "meet", "slice").
* if meet the image is always fully visibile, if slice the viewport is always filled with image.
* @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
* @type String
* @default
*/
meetOrSlice: 'meet',

/**
* Width of a stroke.
* For image quality a stroke multiple of 2 gives better results.
Expand Down Expand Up @@ -142,6 +114,22 @@
*/
cacheKey: '',

/**
* Image crop in pixels from original image size.
* since 2.0.0
* @type Number
* @default
*/
cropX: 0,

/**
* Image crop in pixels from original image size.
* since 2.0.0
* @type Number
* @default
*/
cropY: 0,

/**
* Constructor
* @param {HTMLImageElement | String} element Image element
Expand Down Expand Up @@ -267,7 +255,7 @@
var object = extend(
this.callSuper(
'toObject',
['crossOrigin', 'alignX', 'alignY', 'meetOrSlice'].concat(propertiesToInclude)
['crossOrigin', 'cropX', 'cropY'].concat(propertiesToInclude)
), {
src: this.getSrc(),
filters: filters,
Expand All @@ -289,10 +277,7 @@
*/
toSVG: function(reviver) {
var markup = this._createBaseSVGMarkup(), x = -this.width / 2, y = -this.height / 2,
preserveAspectRatio = 'none', filtered = true;
if (this.alignX !== 'none' && this.alignY !== 'none') {
preserveAspectRatio = 'x' + this.alignX + 'Y' + this.alignY + ' ' + this.meetOrSlice;
}
filtered = true;
markup.push(
'<g transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '">\n',
'<image ', this.getSvgId(), 'xlink:href="', this.getSvgSrc(filtered),
Expand All @@ -303,7 +288,6 @@
// so that object's center aligns with container's left/top
'" width="', this.width,
'" height="', this.height,
'" preserveAspectRatio="', preserveAspectRatio, '"',
'></image>\n'
);

Expand Down Expand Up @@ -465,13 +449,7 @@
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_render: function(ctx) {
var x = -this.width / 2, y = -this.height / 2, imageMargins = this._findMargins(), elementToDraw;

if (this.meetOrSlice === 'slice') {
ctx.beginPath();
ctx.rect(x, y, this.width, this.height);
ctx.clip();
}
var x = -this.width / 2, y = -this.height / 2, elementToDraw;

if (this.isMoving === false && this.resizeFilter && this._needsResize()) {
this._lastScaleX = this.scaleX;
Expand All @@ -480,12 +458,8 @@
}
elementToDraw = this._element;
elementToDraw && ctx.drawImage(elementToDraw,
x + imageMargins.marginX,
y + imageMargins.marginY,
imageMargins.width,
imageMargins.height
);

this.cropX, this.cropY, this.width, this.height,
x, y, this.width, this.height);
this._stroke(ctx);
this._renderStroke(ctx);
},
Expand All @@ -497,40 +471,6 @@
return (this.scaleX !== this._lastScaleX || this.scaleY !== this._lastScaleY);
},

/**
* @private
*/
_findMargins: function() {
var width = this.width, height = this.height, scales,
scale, marginX = 0, marginY = 0;

if (this.alignX !== 'none' || this.alignY !== 'none') {
scales = [this.width / this._element.width, this.height / this._element.height];
scale = this.meetOrSlice === 'meet'
? Math.min.apply(null, scales) : Math.max.apply(null, scales);
width = this._element.width * scale;
height = this._element.height * scale;
if (this.alignX === 'Mid') {
marginX = (this.width - width) / 2;
}
if (this.alignX === 'Max') {
marginX = this.width - width;
}
if (this.alignY === 'Mid') {
marginY = (this.height - height) / 2;
}
if (this.alignY === 'Max') {
marginY = this.height - height;
}
}
return {
width: width,
height: height,
marginX: marginX,
marginY: marginY
};
},

/**
* @private
*/
Expand Down Expand Up @@ -599,6 +539,55 @@
? this.getElement().height || 0
: 0);
},

parsePreserveAspectRatioAttribute: function() {
if (!this.preserveAspectRatio) {
return;
}
var pAR = fabric.util.parsePreserveAspectRatioAttribute(this.preserveAspectRatio),
width = this._element.width, height = this._element.height, scale,
pWidth = this.width, pHeight = this.height, parsedAttributes = { width: pWidth, height: pHeight };
if (pAR && (pAR.alignX !== 'none' || pAR.alignY !== 'none')) {
if (pAR.meetOrSlice === 'meet') {
this.width = width;
this.height = height;
this.scaleX = this.scaleY = scale = fabric.util.findScaleToFit(this._element, parsedAttributes);
if (pAR.alignX === 'Mid') {
this.left += (pWidth - width * scale) / 2;
}
if (pAR.alignX === 'Max') {
this.left += pWidth - width * scale;
}
if (pAR.alignY === 'Mid') {
this.top += (pHeight - height * scale) / 2;
}
if (pAR.alignY === 'Max') {
this.top += pHeight - height * scale;
}
}
if (pAR.meetOrSlice === 'slice') {
this.scaleX = this.scaleY = scale = fabric.util.findScaleToCover(this._element, parsedAttributes);
this.width = pWidth / scale;
this.height = pHeight / scale;
if (pAR.alignX === 'Mid') {
this.cropX = (width - this.width) / 2;
}
if (pAR.alignX === 'Max') {
this.cropX = width - this.width;
}
if (pAR.alignY === 'Mid') {
this.cropY = (height - this.height) / 2;
}
if (pAR.alignY === 'Max') {
this.cropY = height - this.height;
}
}
}
else {
this.scaleX = pWidth / width;
this.scaleY = pHeight / height;
}
}
});

/**
Expand Down Expand Up @@ -669,13 +658,7 @@
* @return {fabric.Image} Instance of fabric.Image
*/
fabric.Image.fromElement = function(element, callback, options) {
var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES),
preserveAR;

if (parsedAttributes.preserveAspectRatio) {
preserveAR = fabric.util.parsePreserveAspectRatioAttribute(parsedAttributes.preserveAspectRatio);
extend(parsedAttributes, preserveAR);
}
var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES);

fabric.Image.fromURL(parsedAttributes['xlink:href'], callback,
extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes));
Expand Down
8 changes: 8 additions & 0 deletions src/util/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,14 @@

capValue: function(min, value, max) {
return Math.max(min, Math.min(value, max));
},

findScaleToFit: function(source, destination) {
return Math.min(destination.width / source.width, destination.height / source.height);
},

findScaleToCover: function(source, destination) {
return Math.max(destination.width / source.width, destination.height / source.height);
}
};

Expand Down
2 changes: 1 addition & 1 deletion test/unit/canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -1006,7 +1006,7 @@
});

asyncTest('loadFromJSON with custom properties on Canvas with image', function() {
var JSON_STRING = '{"objects":[{"type":"image","originX":"left","originY":"top","left":13.6,"top":-1.4,"width":3000,"height":3351,"fill":"rgb(0,0,0)","stroke":null,"strokeWidth":0,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,"scaleX":0.05,"scaleY":0.05,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"src":"' + IMG_SRC + '","filters":[],"crossOrigin":"","alignX":"none","alignY":"none","meetOrSlice":"meet"}],'
var JSON_STRING = '{"objects":[{"type":"image","originX":"left","originY":"top","left":13.6,"top":-1.4,"width":3000,"height":3351,"fill":"rgb(0,0,0)","stroke":null,"strokeWidth":0,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,"scaleX":0.05,"scaleY":0.05,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"src":"' + IMG_SRC + '","filters":[],"crossOrigin":""}],'
+ '"background":"green"}';
var serialized = JSON.parse(JSON_STRING);
serialized.controlsAboveOverlay = true;
Expand Down
7 changes: 3 additions & 4 deletions test/unit/canvas_static.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,8 @@
'crossOrigin': '',
'skewX': 0,
'skewY': 0,
'alignX': 'none',
'alignY': 'none',
'meetOrSlice': 'meet'
'cropX': 0,
'cropY': 0
};

function _createImageElement() {
Expand Down Expand Up @@ -1028,7 +1027,7 @@
asyncTest('loadFromJSON with image background and color', function() {
var serialized = JSON.parse(PATH_JSON);
serialized.background = 'green';
serialized.backgroundImage = JSON.parse('{"type":"image","originX":"left","originY":"top","left":13.6,"top":-1.4,"width":3000,"height":3351,"fill":"rgb(0,0,0)","stroke":null,"strokeWidth":0,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,"scaleX":0.05,"scaleY":0.05,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"src":"' + IMG_SRC + '","filters":[],"crossOrigin":"","alignX":"none","alignY":"none","meetOrSlice":"meet"}');
serialized.backgroundImage = JSON.parse('{"type":"image","originX":"left","originY":"top","left":13.6,"top":-1.4,"width":3000,"height":3351,"fill":"rgb(0,0,0)","stroke":null,"strokeWidth":0,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,"scaleX":0.05,"scaleY":0.05,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"src":"' + IMG_SRC + '","filters":[],"crossOrigin":""}');
canvas.loadFromJSON(serialized, function() {
ok(!canvas.isEmpty(), 'canvas is not empty');
equal(canvas.backgroundColor, 'green');
Expand Down
5 changes: 2 additions & 3 deletions test/unit/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,8 @@
'skewY': 0,
'transformMatrix': null,
'crossOrigin': '',
'alignX': 'none',
'alignY': 'none',
'meetOrSlice': 'meet'
'cropX': 0,
'cropY': 0
};

function _createImageElement() {
Expand Down

0 comments on commit c41ef91

Please sign in to comment.