mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-05-02 02:54:43 +00:00
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:
parent
1435e862d5
commit
ef85ed7eef
7 changed files with 269 additions and 135 deletions
|
|
@ -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);
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue