Ab webgl filter for resize (#4426)

* test another version

* a working webgl resize

* broke blur"

* broke blur"

* a working webgl resize

* better cache shader

* no dist

* better

* better2

* fixed tests

* linting
This commit is contained in:
Andrea Bogazzi 2017-11-04 11:57:05 +01:00 committed by GitHub
parent 1435e862d5
commit ef85ed7eef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 269 additions and 135 deletions

View file

@ -20,11 +20,15 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag
*/
type: 'BaseFilter',
/**
* Array of attributes to send with buffers. do not modify
* @private
*/
vertexSource: 'attribute vec2 aPosition;\n' +
'attribute vec2 aTexCoord;\n' +
'varying vec2 vTexCoord;\n' +
'void main() {\n' +
'vTexCoord = aTexCoord;\n' +
'vTexCoord = aPosition;\n' +
'gl_Position = vec4(aPosition * 2.0 - 1.0, 0.0, 1.0);\n' +
'}',
@ -63,13 +67,12 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag
* @param {String} vertexSource vertexShader source for compilation
*/
createProgram: function(gl, fragmentSource, vertexSource) {
if (!this.vertexSource || !this.fragmentSource) {
return;
if (fabric.webGlPrecision !== 'highp'){
fragmentSource = fragmentSource.replace(
/precision highp float/g,
'precision ' + fabric.webGlPrecision + ' float'
);
}
if(fabric.webGlPrecision !== 'highp'){
fragmentSource = fragmentSource.replace(/precision highp float/g, 'precision ' + fabric.webGlPrecision + ' float');
}
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexSource || this.vertexSource);
gl.compileShader(vertexShader);
@ -125,7 +128,6 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag
getAttributeLocations: function(gl, program) {
return {
aPosition: gl.getAttribLocation(program, 'aPosition'),
aTexCoord: gl.getAttribLocation(program, 'aTexCoord'),
};
},
@ -139,7 +141,8 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag
* @returns {Object} A map of uniform names to uniform locations.
*/
getUniformLocations: function (/* gl, program */) {
// Intentionally left blank, override me in subclasses.
// in case i do not need any special uniform i need to return an empty object
return { };
},
/**
@ -148,20 +151,24 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag
* @param {WebGLRenderingContext} gl The canvas context used to compile the shader program.
* @param {Object} attributeLocations A map of shader attribute names to their locations.
*/
sendAttributeData: function(gl, attributeLocations, squareVertices) {
['aPosition', 'aTexCoord'].forEach(function(attribute) {
var attributeLocation = attributeLocations[attribute];
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.enableVertexAttribArray(attributeLocation);
gl.vertexAttribPointer(attributeLocation, 2, gl.FLOAT, false, 0, 0);
gl.bufferData(gl.ARRAY_BUFFER, squareVertices, gl.STATIC_DRAW);
});
sendAttributeData: function(gl, attributeLocations, aPositionData) {
var attributeLocation = attributeLocations.aPostion;
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.enableVertexAttribArray(attributeLocation);
gl.vertexAttribPointer(attributeLocation, 2, gl.FLOAT, false, 0, 0);
gl.bufferData(gl.ARRAY_BUFFER, aPositionData, gl.STATIC_DRAW);
},
_setupFrameBuffer: function(options) {
var gl = options.context;
var gl = options.context, width, height;
if (options.passes > 1) {
width = options.destinationWidth;
height = options.destinationHeight;
if (options.sourceWidth !== width || options.sourceHeight !== height) {
gl.deleteTexture(options.targetTexture);
options.targetTexture = options.filterBackend.createTexture(gl, width, height);
}
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D,
options.targetTexture, 0);
}
@ -211,7 +218,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag
this.applyToWebGL(options);
this._swapTextures(options);
}
else {
else if (!this.isNeutralState()) {
this.applyTo2d(options);
}
},
@ -251,13 +258,13 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag
gl.bindTexture(gl.TEXTURE_2D, options.sourceTexture);
}
gl.useProgram(shader.program);
this.sendAttributeData(gl, shader.attributeLocations, options.squareVertices);
this.sendAttributeData(gl, shader.attributeLocations, options.aPosition);
gl.uniform1f(shader.uniformLocations.uStepW, 1 / options.sourceWidth);
gl.uniform1f(shader.uniformLocations.uStepH, 1 / options.sourceHeight);
this.sendUniformData(gl, shader.uniformLocations);
gl.viewport(0, 0, options.sourceWidth, options.sourceHeight);
gl.viewport(0, 0, options.destinationWidth, options.destinationHeight);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
},

View file

@ -81,6 +81,20 @@
this.matrix = this.matrix.slice(0);
},
/**
* Intentionally left blank, to be overridden in custom filters
* @param {Object} options
**/
isNeutralState: function(/* options */) {
var _class = filters.ColorMatrix;
for (var i = 20; i--;) {
if (this.matrix[i] !== _class.prototype.matrix[i]) {
return false;
}
}
return true;
},
/**
* Apply the ColorMatrix operation to a Uint8Array representing the pixels of an image.
*

View file

@ -96,6 +96,13 @@
}
},
/**
* Indicate when the filter is not gonna apply changes to the image
**/
isNeutralState: function() {
return this.blocksize === 1;
},
/**
* Return WebGL uniform locations for this filter's shader.
*

View file

@ -56,59 +56,158 @@
*/
lanczosLobes: 3,
// vertexSource: 'attribute vec2 aPosition;\n' +
// 'attribute vec2 aTexCoord;\n' +
// 'uniform float uStepW;\n' +
// 'uniform float uStepH;\n' +
// 'varying vec2 centerTextureCoordinate;\n' +
// 'varying vec2 oneStepLeftTextureCoordinate;\n' +
// 'varying vec2 twoStepsLeftTextureCoordinate;\n' +
// 'varying vec2 threeStepsLeftTextureCoordinate;\n' +
// 'varying vec2 fourStepsLeftTextureCoordinate;\n' +
// 'varying vec2 oneStepRightTextureCoordinate;\n' +
// 'varying vec2 twoStepsRightTextureCoordinate;\n' +
// 'varying vec2 threeStepsRightTextureCoordinate;\n' +
// 'varying vec2 fourStepsRightTextureCoordinate;\n' +
// 'void main() {\n' +
// 'vec2 firstOffset = vec2(uStepW, uStepH);\n' +
// 'vec2 secondOffset = vec2(2.0 * uStepW, 2.0 * uStepH);\n' +
// 'vec2 thirdOffset = vec2(3.0 * uStepW, 3.0 * uStepH);\n' +
// 'vec2 fourthOffset = vec2(4.0 * uStepW, 4.0 * uStepH);\n' +
// 'centerTextureCoordinate = aTexCoord;\n' +
// 'oneStepLeftTextureCoordinate = aTexCoord - firstOffset;\n' +
// 'twoStepsLeftTextureCoordinate = aTexCoord - secondOffset;\n' +
// 'threeStepsLeftTextureCoordinate = aTexCoord - thirdOffset;\n' +
// 'fourStepsLeftTextureCoordinate = aTexCoord - fourthOffset;\n' +
// 'oneStepRightTextureCoordinate = aTexCoord + firstOffset;\n' +
// 'twoStepsRightTextureCoordinate = aTexCoord + secondOffset;\n' +
// 'threeStepsRightTextureCoordinate = aTexCoord + thirdOffset;\n' +
// 'fourStepsRightTextureCoordinate = aTexCoord + fourthOffset;\n' +
// 'gl_Position = vec4(aPosition * 2.0 - 1.0, 0.0, 1.0);\n' +
// '}',
//
// fragmentSource: 'precision highp float;\n' +
// 'varying vec2 centerTextureCoordinate;\n' +
// 'varying vec2 oneStepLeftTextureCoordinate;\n' +
// 'varying vec2 twoStepsLeftTextureCoordinate;\n' +
// 'varying vec2 threeStepsLeftTextureCoordinate;\n' +
// 'varying vec2 fourStepsLeftTextureCoordinate;\n' +
// 'varying vec2 oneStepRightTextureCoordinate;\n' +
// 'varying vec2 twoStepsRightTextureCoordinate;\n' +
// 'varying vec2 threeStepsRightTextureCoordinate;\n' +
// 'varying vec2 fourStepsRightTextureCoordinate;\n' +
// 'uniform sampler2D uTexture;\n' +
// 'void main() {\n' +
// 'vec4 color = texture2D(uTexture, centerTextureCoordinate) * 0.38026;\n' +
// 'color += texture2D(uTexture, oneStepLeftTextureCoordinate) * 0.27667;\n' +
// 'color += texture2D(uTexture, oneStepRightTextureCoordinate) * 0.27667;\n' +
// 'color += texture2D(uTexture, twoStepsLeftTextureCoordinate) * 0.08074;\n' +
// 'color += texture2D(uTexture, twoStepsRightTextureCoordinate) * 0.08074;\n' +
// 'color += texture2D(uTexture, threeStepsLeftTextureCoordinate) * -0.02612;\n' +
// 'color += texture2D(uTexture, threeStepsRightTextureCoordinate) * -0.02612;\n' +
// 'color += texture2D(uTexture, fourStepsLeftTextureCoordinate) * -0.02143;\n' +
// 'color += texture2D(uTexture, fourStepsRightTextureCoordinate) * -0.02143;\n' +
// 'gl_FragColor = color;\n' +
// '}',
/**
* Return WebGL uniform locations for this filter's shader.
*
* @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader.
* @param {WebGLShaderProgram} program This filter's compiled shader program.
*/
getUniformLocations: function(gl, program) {
return {
uDelta: gl.getUniformLocation(program, 'uDelta'),
uTaps: gl.getUniformLocation(program, 'uTaps'),
};
},
/**
* Send data from this filter to its shader program's uniforms.
*
* @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader.
* @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects
*/
sendUniformData: function(gl, uniformLocations) {
gl.uniform2fv(uniformLocations.uDelta, this.horizontal ? [1 / this.width, 0] : [0, 1 / this.height]);
gl.uniform1fv(uniformLocations.uTaps, this.taps);
},
/**
* Retrieves the cached shader.
* @param {Object} options
* @param {WebGLRenderingContext} options.context The GL context used for rendering.
* @param {Object} options.programCache A map of compiled shader programs, keyed by filter type.
*/
retrieveShader: function(options) {
var filterWindow = this.getFilterWindow(), cacheKey = this.type + '_' + filterWindow;
if (!options.programCache.hasOwnProperty(cacheKey)) {
var fragmentShader = this.generateShader(filterWindow);
options.programCache[cacheKey] = this.createProgram(options.context, fragmentShader);
}
return options.programCache[cacheKey];
},
getFilterWindow: function() {
var scale = this.tempScale;
return Math.ceil(this.lanczosLobes / scale);
},
getTaps: function() {
var lobeFunction = this.lanczosCreate(this.lanczosLobes), scale = this.tempScale,
filterWindow = this.getFilterWindow(), taps = new Array(filterWindow);
for (var i = 1; i <= filterWindow; i++) {
taps[i - 1] = lobeFunction(i * scale);
}
return taps;
},
/**
* Generate vertex and shader sources from the necessary steps numbers
* @param {Number} filterWindow
*/
generateShader: function(filterWindow) {
var offsets = new Array(filterWindow),
fragmentShader = this.fragmentSourceTOP, filterWindow;
for (var i = 1; i <= filterWindow; i++) {
offsets[i - 1] = i + '.0 * uDelta';
}
fragmentShader += 'uniform float uTaps[' + filterWindow + '];\n';
fragmentShader += 'void main() {\n';
fragmentShader += ' vec4 color = texture2D(uTexture, vTexCoord);\n';
fragmentShader += ' float sum = 1.0;\n';
offsets.forEach(function(offset, i) {
fragmentShader += ' color += texture2D(uTexture, vTexCoord + ' + offset + ') * uTaps[' + i + '];\n';
fragmentShader += ' color += texture2D(uTexture, vTexCoord - ' + offset + ') * uTaps[' + i + '];\n';
fragmentShader += ' sum += 2.0 * uTaps[' + i + '];\n';
});
fragmentShader += ' gl_FragColor = color / sum;\n';
fragmentShader += '}';
return fragmentShader;
},
fragmentSourceTOP: 'precision highp float;\n' +
'uniform sampler2D uTexture;\n' +
'uniform vec2 uDelta;\n' +
'varying vec2 vTexCoord;\n',
/**
* Apply the resize filter to the image
* Determines whether to use WebGL or Canvas2D based on the options.webgl flag.
*
* @param {Object} options
* @param {Number} options.passes The number of filters remaining to be executed
* @param {Boolean} options.webgl Whether to use webgl to render the filter.
* @param {WebGLTexture} options.sourceTexture The texture setup as the source to be filtered.
* @param {WebGLTexture} options.targetTexture The texture where filtered output should be drawn.
* @param {WebGLRenderingContext} options.context The GL context used for rendering.
* @param {Object} options.programCache A map of compiled shader programs, keyed by filter type.
*/
applyTo: function(options) {
if (options.webgl) {
if (options.passes > 1 && this.isNeutralState(options)) {
// avoid doing something that we do not need
return;
}
options.passes++;
this.width = options.sourceWidth;
this.horizontal = true;
this.dW = Math.round(this.width * this.scaleX);
this.dH = options.sourceHeight;
this.tempScale = this.dW / this.width;
this.taps = this.getTaps();
options.destinationWidth = this.dW;
this._setupFrameBuffer(options);
this.applyToWebGL(options);
this._swapTextures(options);
options.sourceWidth = options.destinationWidth;
this.height = options.sourceHeight;
this.horizontal = false;
this.dH = Math.round(this.height * this.scaleY);
this.tempScale = this.dH / this.height;
this.taps = this.getTaps();
options.destinationHeight = this.dH;
this._setupFrameBuffer(options);
this.applyToWebGL(options);
this._swapTextures(options);
options.sourceHeight = options.destinationHeight;
}
else if (!this.isNeutralState(options)) {
this.applyTo2d(options);
}
},
isNeutralState: function(options) {
var scaleX = options.scaleX || this.scaleX,
scaleY = options.scaleY || this.scaleY;
return scaleX === 1 && scaleY === 1;
},
lanczosCreate: function(lobes) {
return function(x) {
if (x >= lobes || x <= -lobes) {
return 0.0;
}
if (x < 1.19209290E-07 && x > -1.19209290E-07) {
return 1.0;
}
x *= Math.PI;
var xx = x / lobes;
return (sin(x) / x) * sin(xx) / xx;
};
},
/**
* Applies filter to canvas element
@ -121,9 +220,6 @@
var imageData = options.imageData,
scaleX = options.scaleX || this.scaleX,
scaleY = options.scaleY || this.scaleY;
if (scaleX === 1 && scaleY === 1) {
return;
}
this.rcpScaleX = 1 / scaleX;
this.rcpScaleY = 1 / scaleY;
@ -212,20 +308,6 @@
*/
lanczosResize: function(options, oW, oH, dW, dH) {
function lanczosCreate(lobes) {
return function(x) {
if (x > lobes) {
return 0;
}
x *= Math.PI;
if (abs(x) < 1e-16) {
return 1;
}
var xx = x / lobes;
return sin(x) * sin(xx) / x / xx;
};
}
function process(u) {
var v, i, weight, idx, a, red, green,
blue, alpha, fX, fY;
@ -278,9 +360,9 @@
}
var srcData = options.imageData.data,
destImg = options.ctx.creteImageData(dW, dH),
destImg = options.ctx.createImageData(dW, dH),
destData = destImg.data,
lanczos = lanczosCreate(this.lanczosLobes),
lanczos = this.lanczosCreate(this.lanczosLobes),
ratioX = this.rcpScaleX, ratioY = this.rcpScaleY,
rcpRatioX = 2 / this.rcpScaleX, rcpRatioY = 2 / this.rcpScaleY,
range2X = ceil(ratioX * this.lanczosLobes / 2),

View file

@ -13,7 +13,7 @@
var fragmentSource = 'precision ' + precision + ' float;\nvoid main(){}';
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentSource);
gl.compileShader(fragmentShader);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
return false;
}
@ -38,8 +38,8 @@
fabric.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
isSupported = fabric.maxTextureSize >= tileSize;
var precisions = ['highp', 'mediump', 'lowp'];
for(var i = 0; i < 3; i++){
if(testPrecision(gl, precisions[i])){
for (var i = 0; i < 3; i++){
if (testPrecision(gl, precisions[i])){
fabric.webGlPrecision = precisions[i];
break;
};
@ -84,7 +84,7 @@
this.dispose();
this.createWebGLCanvas(width, height);
// eslint-disable-next-line
this.squareVertices = new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]);
this.aPosition = new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]);
this.chooseFastestCopyGLTo2DMethod(width, height);
},
@ -114,17 +114,22 @@
var targetCanvas = fabric.util.createCanvasElement();
// eslint-disable-next-line no-undef
var imageBuffer = new ArrayBuffer(width * height * 4);
var testContext = { imageBuffer: imageBuffer };
var testContext = {
imageBuffer: imageBuffer,
destinationWidth: width,
destinationHeight: height,
targetCanvas: targetCanvas
};
var startTime, drawImageTime, putImageDataTime;
targetCanvas.width = width;
targetCanvas.height = height;
startTime = window.performance.now();
copyGLTo2DDrawImage.call(testContext, this.gl, targetCanvas);
copyGLTo2DDrawImage.call(testContext, this.gl, testContext);
drawImageTime = window.performance.now() - startTime;
startTime = window.performance.now();
copyGLTo2DPutImageData.call(testContext, this.gl, targetCanvas);
copyGLTo2DPutImageData.call(testContext, this.gl, testContext);
putImageDataTime = window.performance.now() - startTime;
if (drawImageTime > putImageDataTime) {
@ -181,6 +186,8 @@
originalHeight: source.height || source.originalHeight,
sourceWidth: width,
sourceHeight: height,
destinationWidth: width,
destinationHeight: height,
context: gl,
sourceTexture: this.createTexture(gl, width, height, !cachedTexture && source),
targetTexture: this.createTexture(gl, width, height),
@ -188,15 +195,17 @@
this.createTexture(gl, width, height, !cachedTexture && source),
passes: filters.length,
webgl: true,
squareVertices: this.squareVertices,
aPosition: this.aPosition,
programCache: this.programCache,
pass: 0,
filterBackend: this
filterBackend: this,
targetCanvas: targetCanvas
};
var tempFbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, tempFbo);
filters.forEach(function(filter) { filter && filter.applyTo(pipelineState); });
this.copyGLTo2D(gl, targetCanvas);
resizeCanvasIfNeeded(pipelineState);
this.copyGLTo2D(gl, pipelineState);
gl.bindTexture(gl.TEXTURE_2D, null);
gl.deleteTexture(pipelineState.sourceTexture);
gl.deleteTexture(pipelineState.targetTexture);
@ -283,8 +292,8 @@
createTexture: function(gl, width, height, textureImageSource) {
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
if (textureImageSource) {
@ -362,6 +371,18 @@
};
})();
function resizeCanvasIfNeeded(pipelineState) {
var targetCanvas = pipelineState.targetCanvas,
width = targetCanvas.width, height = targetCanvas.height,
dWidth = pipelineState.destinationWidth,
dHeight = pipelineState.destinationHeight;
if (width !== dWidth || height !== dHeight) {
targetCanvas.width = dWidth;
targetCanvas.height = dHeight;
}
}
/**
* Copy an input WebGL canvas on to an output 2D canvas.
*
@ -370,15 +391,16 @@
*
* @param {WebGLRenderingContext} sourceContext The WebGL context to copy from.
* @param {HTMLCanvasElement} targetCanvas The 2D target canvas to copy on to.
* @param {Object} pipelineState The 2D target canvas to copy on to.
*/
function copyGLTo2DDrawImage(gl, targetCanvas) {
var sourceCanvas = gl.canvas;
var ctx = targetCanvas.getContext('2d');
function copyGLTo2DDrawImage(gl, pipelineState) {
var glCanvas = gl.canvas, targetCanvas = pipelineState.targetCanvas,
ctx = targetCanvas.getContext('2d');
ctx.translate(0, targetCanvas.height); // move it down again
ctx.scale(1, -1); // vertical flip
// where is my image on the big glcanvas?
var sourceY = sourceCanvas.height - targetCanvas.height;
ctx.drawImage(sourceCanvas, 0, sourceY, targetCanvas.width, targetCanvas.height, 0, 0,
var sourceY = glCanvas.height - targetCanvas.height;
ctx.drawImage(glCanvas, 0, sourceY, targetCanvas.width, targetCanvas.height, 0, 0,
targetCanvas.width, targetCanvas.height);
}
@ -388,17 +410,20 @@ function copyGLTo2DDrawImage(gl, targetCanvas) {
*
* @param {WebGLRenderingContext} sourceContext The WebGL context to copy from.
* @param {HTMLCanvasElement} targetCanvas The 2D target canvas to copy on to.
* @param {Object} pipelineState The 2D target canvas to copy on to.
*/
function copyGLTo2DPutImageData(gl, targetCanvas) {
var ctx = targetCanvas.getContext('2d');
var width = targetCanvas.width;
var height = targetCanvas.height;
var numBytes = width * height * 4;
function copyGLTo2DPutImageData(gl, pipelineState) {
var targetCanvas = pipelineState.targetCanvas, ctx = targetCanvas.getContext('2d'),
dWidth = pipelineState.destinationWidth,
dHeight = pipelineState.destinationHeight,
numBytes = dWidth * dHeight * 4;
// eslint-disable-next-line no-undef
var u8 = new Uint8Array(this.imageBuffer, 0, numBytes);
// eslint-disable-next-line no-undef
var u8Clamped = new Uint8ClampedArray(this.imageBuffer, 0, numBytes);
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, u8);
var imgData = new ImageData(u8Clamped, width);
gl.readPixels(0, 0, dWidth, dHeight, gl.RGBA, gl.UNSIGNED_BYTE, u8);
var imgData = new ImageData(u8Clamped, dWidth, dHeight);
ctx.putImageData(imgData, 0, 0);
}

View file

@ -267,9 +267,6 @@
if (this.resizeFilter) {
object.resizeFilter = this.resizeFilter.toObject();
}
object.width /= this._filterScalingX;
object.height /= this._filterScalingY;
return object;
},
@ -443,11 +440,10 @@
}
fabric.filterBackend.applyFilters(
filters, this._originalElement, sourceWidth, sourceHeight, this._element, this.cacheKey);
if (this.width !== this._element.width || this.height !== this._element.height) {
this._filterScalingX = this._element.width / this.width;
this._filterScalingY = this._element.height / this.height;
this.width = this._element.width;
this.height = this._element.height;
if (this._originalElement.width !== this._element.width ||
this._originalElement.height !== this._element.height) {
this._filterScalingX = this._element.width / this._originalElement.width;
this._filterScalingY = this._element.height / this._originalElement.height;
}
return this;
},
@ -467,11 +463,14 @@
},
_renderFill: function(ctx) {
var x = -this.width / 2, y = -this.height / 2, elementToDraw;
elementToDraw = this._element;
var w = this.width, h = this.height, sW = w * this._filterScalingX, sH = h * this._filterScalingY,
x = -w / 2, y = -h / 2, elementToDraw = this._element;
elementToDraw && ctx.drawImage(elementToDraw,
this.cropX, this.cropY, this.width, this.height,
x, y, this.width, this.height);
this.cropX * this._filterScalingX,
this.cropY * this._filterScalingY,
sW,
sH,
x, y, w, h);
},
/**

View file

@ -185,8 +185,10 @@
var width = image.width, height = image.height;
assert.ok(image.filters[0] instanceof fabric.Image.filters.Resize, 'should inherit from fabric.Image.filters.Resize');
image.applyFilters();
assert.equal(image.width, Math.floor(width / 5), 'width should be a fifth');
assert.equal(image.height, Math.floor(height / 5), 'height should a fifth');
assert.equal(image.width, Math.floor(width), 'width is not changed');
assert.equal(image.height, Math.floor(height), 'height is not changed');
assert.equal(image._filterScalingX.toFixed(1), 0.2, 'a new scaling factor is made for x');
assert.equal(image._filterScalingY.toFixed(1), 0.2, 'a new scaling factor is made for y');
var toObject = image.toObject();
assert.deepEqual(toObject.filters[0], filter.toObject());
assert.equal(toObject.width, width, 'width is stored as before filters');
@ -196,8 +198,6 @@
assert.ok(filterFromObj instanceof fabric.Image.filters.Resize, 'should inherit from fabric.Image.filters.Resize');
assert.equal(filterFromObj.scaleY, 0.2);
assert.equal(filterFromObj.scaleX, 0.2);
assert.equal(_imageFromObject.width, Math.floor(width / 5), 'on image reload width is halved again');
assert.equal(_imageFromObject.height, Math.floor(height / 5), 'on image reload width is halved again');
done();
});
});