mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-05-22 20:25:49 +00:00
Resize filter should include canvas zoom and group zoom (#5117)
* change in zoom * added a test * fixed * disable gl * add percentage * more group things * more group things * added unit test * display percentage * lint
This commit is contained in:
parent
a37e802ed7
commit
6353b09c5f
7 changed files with 226 additions and 17 deletions
|
|
@ -30,6 +30,8 @@
|
|||
|
||||
/**
|
||||
* Resize type
|
||||
* for webgl resizyType is just lanczos, for canvas2d can be:
|
||||
* bilinear, hermite, sliceHacl, lanczos.
|
||||
* @param {String} resizeType
|
||||
* @default
|
||||
*/
|
||||
|
|
@ -50,7 +52,7 @@
|
|||
scaleY: 0,
|
||||
|
||||
/**
|
||||
* LanczosLobes parameter for lanczos filter
|
||||
* LanczosLobes parameter for lanczos filter, valid for resizeType lanczos
|
||||
* @param {Number} lanczosLobes
|
||||
* @default
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -385,10 +385,10 @@
|
|||
|
||||
applyResizeFilters: function() {
|
||||
var filter = this.resizeFilter,
|
||||
retinaScaling = this.canvas ? this.canvas.getRetinaScaling() : 1,
|
||||
minimumScale = this.minimumScaleTrigger,
|
||||
scaleX = this.scaleX * retinaScaling,
|
||||
scaleY = this.scaleY * retinaScaling,
|
||||
objectScale = this.getTotalObjectScaling(),
|
||||
scaleX = objectScale.scaleX,
|
||||
scaleY = objectScale.scaleY,
|
||||
elementToFilter = this._filteredEl || this._originalElement;
|
||||
if (this.group) {
|
||||
this.set('dirty', true);
|
||||
|
|
@ -397,6 +397,8 @@
|
|||
this._element = elementToFilter;
|
||||
this._filterScalingX = 1;
|
||||
this._filterScalingY = 1;
|
||||
this._lastScaleX = 1;
|
||||
this._lastScaleY = 1;
|
||||
return;
|
||||
}
|
||||
if (!fabric.filterBackend) {
|
||||
|
|
@ -408,8 +410,8 @@
|
|||
canvasEl.width = sourceWidth;
|
||||
canvasEl.height = sourceHeight;
|
||||
this._element = canvasEl;
|
||||
filter.scaleX = scaleX;
|
||||
filter.scaleY = scaleY;
|
||||
this._lastScaleX = filter.scaleX = scaleX;
|
||||
this._lastScaleY = filter.scaleY = scaleY;
|
||||
fabric.filterBackend.applyFilters(
|
||||
[filter], elementToFilter, sourceWidth, sourceHeight, this._element, cacheKey);
|
||||
this._filterScalingX = canvasEl.width / this._originalElement.width;
|
||||
|
|
@ -473,9 +475,7 @@
|
|||
* @param {CanvasRenderingContext2D} ctx Context to render on
|
||||
*/
|
||||
_render: function(ctx) {
|
||||
if (this.isMoving === false && this.resizeFilter && this._needsResize()) {
|
||||
this._lastScaleX = this.scaleX;
|
||||
this._lastScaleY = this.scaleY;
|
||||
if (this.isMoving !== true && this.resizeFilter && this._needsResize()) {
|
||||
this.applyResizeFilters();
|
||||
}
|
||||
this._stroke(ctx);
|
||||
|
|
@ -497,7 +497,8 @@
|
|||
* @private, needed to check if image needs resize
|
||||
*/
|
||||
_needsResize: function() {
|
||||
return (this.scaleX !== this._lastScaleX || this.scaleY !== this._lastScaleY);
|
||||
var scale = this.getTotalObjectScaling();
|
||||
return (scale.scaleX !== this._lastScaleX || scale.scaleY !== this._lastScaleY);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -678,12 +678,10 @@
|
|||
* @return {Object}.zoomY zoomY zoom value to unscale the canvas before drawing cache
|
||||
*/
|
||||
_getCacheCanvasDimensions: function() {
|
||||
var zoom = this.canvas && this.canvas.getZoom() || 1,
|
||||
objectScale = this.getObjectScaling(),
|
||||
retina = this.canvas && this.canvas._isRetinaScaling() ? fabric.devicePixelRatio : 1,
|
||||
var objectScale = this.getTotalObjectScaling(),
|
||||
dim = this._getNonTransformedDimensions(),
|
||||
zoomX = objectScale.scaleX * zoom * retina,
|
||||
zoomY = objectScale.scaleY * zoom * retina,
|
||||
zoomX = objectScale.scaleX,
|
||||
zoomY = objectScale.scaleY,
|
||||
width = dim.x * zoomX,
|
||||
height = dim.y * zoomY;
|
||||
return {
|
||||
|
|
@ -890,6 +888,21 @@
|
|||
return { scaleX: scaleX, scaleY: scaleY };
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the object scale factor counting also the group scaling, zoom and retina
|
||||
* @return {Object} object with scaleX and scaleY properties
|
||||
*/
|
||||
getTotalObjectScaling: function() {
|
||||
var scale = this.getObjectScaling(), scaleX = scale.scaleX, scaleY = scale.scaleY;
|
||||
if (this.canvas) {
|
||||
var zoom = this.canvas.getZoom();
|
||||
var retina = this.canvas.getRetinaScaling();
|
||||
scaleX *= zoom * retina;
|
||||
scaleY *= zoom * retina;
|
||||
}
|
||||
return { scaleX: scaleX, scaleY: scaleY };
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the object opacity counting also the group property
|
||||
* @return {Number}
|
||||
|
|
|
|||
BIN
test/fixtures/parrot.png
vendored
Normal file
BIN
test/fixtures/parrot.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 671 KiB |
|
|
@ -52,6 +52,9 @@
|
|||
fabric.perfLimitSizeTotal = 2097152;
|
||||
fabric.maxCacheSideLimit = 4096;
|
||||
fabric.minCacheSideLimit = 256;
|
||||
fabric.devicePixelRatio = 1;
|
||||
canvas.enableRetinaScaling = false;
|
||||
canvas.setZoom(1);
|
||||
canvas.clear();
|
||||
canvas.backgroundColor = fabric.Canvas.prototype.backgroundColor;
|
||||
canvas.calcOffset();
|
||||
|
|
@ -989,13 +992,30 @@
|
|||
assert.equal(typeof deserializedObject.clipTo, 'function');
|
||||
});
|
||||
|
||||
QUnit.test('getObjectScale', function(assert) {
|
||||
QUnit.test('getTotalObjectScaling with zoom', function(assert) {
|
||||
var object = new fabric.Object({ scaleX: 3, scaleY: 2});
|
||||
canvas.setZoom(3);
|
||||
canvas.add(object);
|
||||
var objectScale = object.getTotalObjectScaling();
|
||||
assert.deepEqual(objectScale, { scaleX: object.scaleX * 3, scaleY: object.scaleY * 3 });
|
||||
});
|
||||
|
||||
QUnit.test('getTotalObjectScaling with retina', function(assert) {
|
||||
var object = new fabric.Object({ scaleX: 3, scaleY: 2});
|
||||
canvas.enableRetinaScaling = true;
|
||||
fabric.devicePixelRatio = 4;
|
||||
canvas.add(object);
|
||||
var objectScale = object.getTotalObjectScaling();
|
||||
assert.deepEqual(objectScale, { scaleX: object.scaleX * 4, scaleY: object.scaleY * 4 });
|
||||
});
|
||||
|
||||
QUnit.test('getObjectScaling', function(assert) {
|
||||
var object = new fabric.Object({ scaleX: 3, scaleY: 2});
|
||||
var objectScale = object.getObjectScaling();
|
||||
assert.deepEqual(objectScale, {scaleX: object.scaleX, scaleY: object.scaleY});
|
||||
});
|
||||
|
||||
QUnit.test('getObjectScale in group', function(assert) {
|
||||
QUnit.test('getObjectScaling in group', function(assert) {
|
||||
var object = new fabric.Object({ scaleX: 3, scaleY: 2});
|
||||
var group = new fabric.Group();
|
||||
group.scaleX = 2;
|
||||
|
|
|
|||
BIN
test/visual/golden/parrot.png
Normal file
BIN
test/visual/golden/parrot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
173
test/visual/resize_filter.js
Normal file
173
test/visual/resize_filter.js
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
(function() {
|
||||
fabric.enableGLFiltering = false;
|
||||
var _pixelMatch = pixelmatch;
|
||||
if (fabric.isLikelyNode) {
|
||||
var fs = global.fs;
|
||||
_pixelMatch = global.pixelmatch;
|
||||
}
|
||||
var fabricCanvas = this.canvas = new fabric.Canvas(null, {enableRetinaScaling: false, renderOnAddRemove: false});
|
||||
var pixelmatchOptions = {
|
||||
includeAA: false,
|
||||
threshold: 0.095
|
||||
};
|
||||
fabric.Object.prototype.objectCaching = false;
|
||||
|
||||
function getAbsolutePath(path) {
|
||||
var isAbsolute = /^https?:/.test(path);
|
||||
if (isAbsolute) { return path; };
|
||||
var imgEl = fabric.document.createElement('img');
|
||||
imgEl.src = path;
|
||||
var src = imgEl.src;
|
||||
imgEl = null;
|
||||
return src;
|
||||
}
|
||||
|
||||
function getFixtureName(filename) {
|
||||
var finalName = '/../fixtures/' + filename;
|
||||
return fabric.isLikelyNode ? (__dirname + finalName) : getAbsolutePath('test/fixtures/' + filename);
|
||||
}
|
||||
|
||||
function getGoldeName(filename) {
|
||||
var finalName = '/golden/' + filename;
|
||||
return fabric.isLikelyNode ? (__dirname + finalName) : getAbsolutePath('test/visual/golden/' + filename);
|
||||
}
|
||||
|
||||
function getImage(filename, original, callback) {
|
||||
if (fabric.isLikelyNode && original) {
|
||||
try {
|
||||
fs.statSync(filename);
|
||||
}
|
||||
catch (err) {
|
||||
var dataUrl = original.toDataURL().split(',')[1];
|
||||
console.log('creating original for ', filename);
|
||||
fs.writeFileSync(filename, dataUrl, { encoding: 'base64' });
|
||||
}
|
||||
}
|
||||
var img = fabric.document.createElement('img');
|
||||
img.onload = function() {
|
||||
callback(img);
|
||||
};
|
||||
img.onerror = function(err) {
|
||||
console.log('Image loading errored', err);
|
||||
};
|
||||
img.src = filename;
|
||||
}
|
||||
|
||||
QUnit.module('Image resize filter test', {
|
||||
afterEach: function() {
|
||||
fabricCanvas.setZoom(1);
|
||||
fabricCanvas.setDimensions({
|
||||
width: 300,
|
||||
height: 150,
|
||||
});
|
||||
fabricCanvas.clear();
|
||||
fabricCanvas.renderAll();
|
||||
}
|
||||
});
|
||||
|
||||
var tests = [];
|
||||
|
||||
function imageResizeTest(canvas, callback) {
|
||||
getImage(getFixtureName('parrot.png'), false, function(img) {
|
||||
canvas.setDimensions({
|
||||
width: 200,
|
||||
height: 200,
|
||||
});
|
||||
var zoom = 8;
|
||||
var image = new fabric.Image(img);
|
||||
image.resizeFilter = new fabric.Image.filters.Resize({ resizeType: 'lanczos' });
|
||||
canvas.setZoom(zoom);
|
||||
image.scaleToWidth(canvas.width / zoom);
|
||||
canvas.add(image);
|
||||
canvas.renderAll();
|
||||
callback(canvas.lowerCanvasEl);
|
||||
});
|
||||
}
|
||||
|
||||
tests.push({
|
||||
test: 'Image resize with canvas zoom',
|
||||
code: imageResizeTest,
|
||||
golden: 'parrot.png',
|
||||
percentage: 0.06,
|
||||
});
|
||||
|
||||
function imageResizeTestNoZoom(canvas, callback) {
|
||||
getImage(getFixtureName('parrot.png'), false, function(img) {
|
||||
canvas.setDimensions({
|
||||
width: 200,
|
||||
height: 200,
|
||||
});
|
||||
var image = new fabric.Image(img);
|
||||
image.resizeFilter = new fabric.Image.filters.Resize({ resizeType: 'lanczos' });
|
||||
image.scaleToWidth(canvas.width);
|
||||
canvas.add(image);
|
||||
canvas.renderAll();
|
||||
callback(canvas.lowerCanvasEl);
|
||||
});
|
||||
}
|
||||
|
||||
tests.push({
|
||||
test: 'Image resize without zoom',
|
||||
code: imageResizeTestNoZoom,
|
||||
golden: 'parrot.png',
|
||||
percentage: 0.06,
|
||||
});
|
||||
|
||||
function imageResizeTestGroup(canvas, callback) {
|
||||
getImage(getFixtureName('parrot.png'), false, function(img) {
|
||||
canvas.setDimensions({
|
||||
width: 200,
|
||||
height: 200,
|
||||
});
|
||||
var image = new fabric.Image(img, { strokeWidth: 0 });
|
||||
image.resizeFilter = new fabric.Image.filters.Resize({ resizeType: 'lanczos' });
|
||||
var group = new fabric.Group([image]);
|
||||
group.strokeWidth = 0;
|
||||
group.scaleToWidth(canvas.width);
|
||||
canvas.add(group);
|
||||
canvas.renderAll();
|
||||
callback(canvas.lowerCanvasEl);
|
||||
});
|
||||
}
|
||||
|
||||
tests.push({
|
||||
test: 'Image resize with scaled group',
|
||||
code: imageResizeTestGroup,
|
||||
golden: 'parrot.png',
|
||||
percentage: 0.06,
|
||||
});
|
||||
|
||||
tests.forEach(function(testObj) {
|
||||
var testName = testObj.test;
|
||||
var code = testObj.code;
|
||||
var percentage = testObj.percentage;
|
||||
var golden = testObj.golden;
|
||||
QUnit.test(testName, function(assert) {
|
||||
var done = assert.async();
|
||||
code(fabricCanvas, function(renderedCanvas) {
|
||||
var width = renderedCanvas.width;
|
||||
var height = renderedCanvas.height;
|
||||
var totalPixels = width * height;
|
||||
var imageDataCanvas = renderedCanvas.getContext('2d').getImageData(0, 0, width, height).data;
|
||||
var canvas = fabric.document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
var ctx = canvas.getContext('2d');
|
||||
var output = ctx.getImageData(0, 0, width, height).data;
|
||||
getImage(getGoldeName(golden), renderedCanvas, function(golden) {
|
||||
ctx.drawImage(golden, 0, 0);
|
||||
var imageDataGolden = ctx.getImageData(0, 0, width, height).data;
|
||||
var differentPixels = _pixelMatch(imageDataCanvas, imageDataGolden, output, width, height, pixelmatchOptions);
|
||||
var percDiff = differentPixels / totalPixels * 100;
|
||||
var okDiff = totalPixels * percentage;
|
||||
assert.ok(
|
||||
differentPixels < okDiff,
|
||||
testName + ' has too many different pixels ' + differentPixels + '(' + okDiff + ') representing ' + percDiff + '%'
|
||||
);
|
||||
console.log('Different pixels:', differentPixels, '/', totalPixels, ' diff:', percDiff.toFixed(3), '%');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
Loading…
Reference in a new issue