mirror of
https://github.com/konvajs/konva.git
synced 2025-04-05 20:48:28 +08:00
1536 lines
40 KiB
JavaScript
1536 lines
40 KiB
JavaScript
/**
|
|
* WebGL-2D.js - HTML5 Canvas2D API in a WebGL context
|
|
*
|
|
* Created by Corban Brook <corbanbrook@gmail.com> on 2011-03-02.
|
|
* Amended to by Bobby Richter <secretrobotron@gmail.com> on 2011-03-03
|
|
* CubicVR.js by Charles Cliffe <cj@cubicproductions.com> on 2011-03-03
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2011 Corban Brook
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
* the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* Usage:
|
|
*
|
|
* var cvs = document.getElementById("myCanvas");
|
|
*
|
|
* WebGL2D.enable(cvs); // adds "webgl-2d" to cvs
|
|
*
|
|
* cvs.getContext("webgl-2d");
|
|
*
|
|
*/
|
|
|
|
(function(Math, undefined) {
|
|
// Vector & Matrix libraries from CubicVR.js
|
|
var M_PI = 3.1415926535897932384626433832795028841968;
|
|
var M_TWO_PI = 2.0 * M_PI;
|
|
var M_HALF_PI = M_PI / 2.0;
|
|
|
|
function isPOT(value) {
|
|
return value > 0 && ((value - 1) & value) === 0;
|
|
}
|
|
|
|
var vec3 = {
|
|
length: function(pt) {
|
|
return Math.sqrt(pt[0] * pt[0] + pt[1] * pt[1] + pt[2] * pt[2]);
|
|
},
|
|
|
|
normalize: function(pt) {
|
|
var d = Math.sqrt(pt[0] * pt[0] + pt[1] * pt[1] + pt[2] * pt[2]);
|
|
if (d === 0) {
|
|
return [0, 0, 0];
|
|
}
|
|
return [pt[0] / d, pt[1] / d, pt[2] / d];
|
|
},
|
|
|
|
dot: function(v1, v2) {
|
|
return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
|
|
},
|
|
|
|
angle: function(v1, v2) {
|
|
return Math.acos(
|
|
(v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]) /
|
|
(Math.sqrt(v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2]) *
|
|
Math.sqrt(v2[0] * v2[0] + v2[1] * v2[1] + v2[2] * v2[2]))
|
|
);
|
|
},
|
|
|
|
cross: function(vectA, vectB) {
|
|
return [
|
|
vectA[1] * vectB[2] - vectB[1] * vectA[2],
|
|
vectA[2] * vectB[0] - vectB[2] * vectA[0],
|
|
vectA[0] * vectB[1] - vectB[0] * vectA[1]
|
|
];
|
|
},
|
|
|
|
multiply: function(vectA, constB) {
|
|
return [vectA[0] * constB, vectA[1] * constB, vectA[2] * constB];
|
|
},
|
|
|
|
add: function(vectA, vectB) {
|
|
return [vectA[0] + vectB[0], vectA[1] + vectB[1], vectA[2] + vectB[2]];
|
|
},
|
|
|
|
subtract: function(vectA, vectB) {
|
|
return [vectA[0] - vectB[0], vectA[1] - vectB[1], vectA[2] - vectB[2]];
|
|
},
|
|
|
|
equal: function(a, b) {
|
|
var epsilon = 0.0000001;
|
|
if (a === undefined && b === undefined) {
|
|
return true;
|
|
}
|
|
if (a === undefined || b === undefined) {
|
|
return false;
|
|
}
|
|
return (
|
|
Math.abs(a[0] - b[0]) < epsilon &&
|
|
Math.abs(a[1] - b[1]) < epsilon &&
|
|
Math.abs(a[2] - b[2]) < epsilon
|
|
);
|
|
}
|
|
};
|
|
|
|
var mat3 = {
|
|
identity: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
|
|
|
|
multiply: function(m1, m2) {
|
|
var m10 = m1[0],
|
|
m11 = m1[1],
|
|
m12 = m1[2],
|
|
m13 = m1[3],
|
|
m14 = m1[4],
|
|
m15 = m1[5],
|
|
m16 = m1[6],
|
|
m17 = m1[7],
|
|
m18 = m1[8],
|
|
m20 = m2[0],
|
|
m21 = m2[1],
|
|
m22 = m2[2],
|
|
m23 = m2[3],
|
|
m24 = m2[4],
|
|
m25 = m2[5],
|
|
m26 = m2[6],
|
|
m27 = m2[7],
|
|
m28 = m2[8];
|
|
|
|
m2[0] = m20 * m10 + m23 * m11 + m26 * m12;
|
|
m2[1] = m21 * m10 + m24 * m11 + m27 * m12;
|
|
m2[2] = m22 * m10 + m25 * m11 + m28 * m12;
|
|
m2[3] = m20 * m13 + m23 * m14 + m26 * m15;
|
|
m2[4] = m21 * m13 + m24 * m14 + m27 * m15;
|
|
m2[5] = m22 * m13 + m25 * m14 + m28 * m15;
|
|
m2[6] = m20 * m16 + m23 * m17 + m26 * m18;
|
|
m2[7] = m21 * m16 + m24 * m17 + m27 * m18;
|
|
m2[8] = m22 * m16 + m25 * m17 + m28 * m18;
|
|
},
|
|
|
|
vec2_multiply: function(m1, m2) {
|
|
var mOut = [];
|
|
mOut[0] = m2[0] * m1[0] + m2[3] * m1[1] + m2[6];
|
|
mOut[1] = m2[1] * m1[0] + m2[4] * m1[1] + m2[7];
|
|
return mOut;
|
|
},
|
|
|
|
transpose: function(m) {
|
|
return [m[0], m[3], m[6], m[1], m[4], m[7], m[2], m[5], m[8]];
|
|
}
|
|
}; //mat3
|
|
|
|
// Transform library from CubicVR.js
|
|
function Transform(mat) {
|
|
return this.clearStack(mat);
|
|
}
|
|
|
|
var STACK_DEPTH_LIMIT = 16;
|
|
|
|
Transform.prototype.clearStack = function(init_mat) {
|
|
this.m_stack = [];
|
|
this.m_cache = [];
|
|
this.c_stack = 0;
|
|
this.valid = 0;
|
|
this.result = null;
|
|
|
|
for (var i = 0; i < STACK_DEPTH_LIMIT; i++) {
|
|
this.m_stack[i] = this.getIdentity();
|
|
}
|
|
|
|
if (init_mat !== undefined) {
|
|
this.m_stack[0] = init_mat;
|
|
} else {
|
|
this.setIdentity();
|
|
}
|
|
}; //clearStack
|
|
|
|
Transform.prototype.setIdentity = function() {
|
|
this.m_stack[this.c_stack] = this.getIdentity();
|
|
if (this.valid === this.c_stack && this.c_stack) {
|
|
this.valid--;
|
|
}
|
|
};
|
|
|
|
Transform.prototype.getIdentity = function() {
|
|
return [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
|
|
};
|
|
|
|
Transform.prototype.getResult = function() {
|
|
if (!this.c_stack) {
|
|
return this.m_stack[0];
|
|
}
|
|
|
|
var m = mat3.identity;
|
|
|
|
if (this.valid > this.c_stack - 1) {
|
|
this.valid = this.c_stack - 1;
|
|
}
|
|
|
|
for (var i = this.valid; i < this.c_stack + 1; i++) {
|
|
m = mat3.multiply(this.m_stack[i], m);
|
|
this.m_cache[i] = m;
|
|
}
|
|
|
|
this.valid = this.c_stack - 1;
|
|
|
|
this.result = this.m_cache[this.c_stack];
|
|
|
|
return this.result;
|
|
};
|
|
|
|
Transform.prototype.pushMatrix = function() {
|
|
this.c_stack++;
|
|
this.m_stack[this.c_stack] = this.getIdentity();
|
|
};
|
|
|
|
Transform.prototype.popMatrix = function() {
|
|
if (this.c_stack === 0) {
|
|
return;
|
|
}
|
|
this.c_stack--;
|
|
};
|
|
|
|
var translateMatrix = Transform.prototype.getIdentity();
|
|
|
|
Transform.prototype.translate = function(x, y) {
|
|
translateMatrix[6] = x;
|
|
translateMatrix[7] = y;
|
|
|
|
mat3.multiply(translateMatrix, this.m_stack[this.c_stack]);
|
|
|
|
/*
|
|
if (this.valid === this.c_stack && this.c_stack) {
|
|
this.valid--;
|
|
}
|
|
*/
|
|
};
|
|
|
|
var scaleMatrix = Transform.prototype.getIdentity();
|
|
|
|
Transform.prototype.scale = function(x, y) {
|
|
scaleMatrix[0] = x;
|
|
scaleMatrix[4] = y;
|
|
|
|
mat3.multiply(scaleMatrix, this.m_stack[this.c_stack]);
|
|
|
|
/*
|
|
if (this.valid === this.c_stack && this.c_stack) {
|
|
this.valid--;
|
|
}
|
|
*/
|
|
};
|
|
|
|
var rotateMatrix = Transform.prototype.getIdentity();
|
|
|
|
Transform.prototype.rotate = function(ang) {
|
|
var sAng, cAng;
|
|
|
|
sAng = Math.sin(-ang);
|
|
cAng = Math.cos(-ang);
|
|
|
|
rotateMatrix[0] = cAng;
|
|
rotateMatrix[3] = sAng;
|
|
rotateMatrix[1] = -sAng;
|
|
rotateMatrix[4] = cAng;
|
|
|
|
mat3.multiply(rotateMatrix, this.m_stack[this.c_stack]);
|
|
|
|
/*
|
|
if (this.valid === this.c_stack && this.c_stack) {
|
|
this.valid--;
|
|
}
|
|
*/
|
|
};
|
|
|
|
var WebGL2D = (this.WebGL2D = function WebGL2D(canvas, options) {
|
|
this.canvas = canvas;
|
|
this.options = options || {};
|
|
this.gl = undefined;
|
|
this.fs = undefined;
|
|
this.vs = undefined;
|
|
this.shaderProgram = undefined;
|
|
this.transform = new Transform();
|
|
this.shaderPool = [];
|
|
this.maxTextureSize = undefined;
|
|
|
|
// Save a reference to the WebGL2D instance on the canvas object
|
|
canvas.gl2d = this;
|
|
|
|
// Store getContext function for later use
|
|
canvas.$getContext = canvas.getContext;
|
|
// Override getContext function with "webgl-2d" enabled version
|
|
canvas.getContext = (function(gl2d) {
|
|
return function(context) {
|
|
if (
|
|
(gl2d.options.force || context === 'webgl-2d') &&
|
|
!(canvas.width === 0 || canvas.height === 0)
|
|
) {
|
|
if (gl2d.gl) {
|
|
return gl2d.gl;
|
|
}
|
|
|
|
var gl = (gl2d.gl = gl2d.canvas.$getContext('experimental-webgl'));
|
|
|
|
gl2d.initShaders();
|
|
gl2d.initBuffers();
|
|
|
|
// Append Canvas2D API features to the WebGL context
|
|
gl2d.initCanvas2DAPI();
|
|
|
|
gl.viewport(0, 0, gl2d.canvas.width, gl2d.canvas.height);
|
|
|
|
// Default white background
|
|
gl.clearColor(1, 1, 1, 1);
|
|
gl.clear(gl.COLOR_BUFFER_BIT); // | gl.DEPTH_BUFFER_BIT);
|
|
|
|
// Disables writing to dest-alpha
|
|
gl.colorMask(1, 1, 1, 0);
|
|
|
|
// Depth options
|
|
//gl.enable(gl.DEPTH_TEST);
|
|
//gl.depthFunc(gl.LEQUAL);
|
|
|
|
// Blending options
|
|
gl.enable(gl.BLEND);
|
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
|
|
gl2d.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
|
|
|
|
return gl;
|
|
} else {
|
|
return gl2d.canvas.$getContext(context);
|
|
}
|
|
};
|
|
})(this);
|
|
|
|
this.postInit();
|
|
});
|
|
|
|
// Enables WebGL2D on your canvas
|
|
WebGL2D.enable = function(canvas, options) {
|
|
return canvas.gl2d || new WebGL2D(canvas, options);
|
|
};
|
|
|
|
// Shader Pool BitMasks, i.e. sMask = (shaderMask.texture+shaderMask.stroke)
|
|
var shaderMask = {
|
|
texture: 1,
|
|
crop: 2,
|
|
path: 4
|
|
};
|
|
|
|
// Fragment shader source
|
|
WebGL2D.prototype.getFragmentShaderSource = function getFragmentShaderSource(
|
|
sMask
|
|
) {
|
|
var fsSource = [
|
|
'#ifdef GL_ES',
|
|
'precision highp float;',
|
|
'#endif',
|
|
|
|
'#define hasTexture ' + (sMask & shaderMask.texture ? '1' : '0'),
|
|
'#define hasCrop ' + (sMask & shaderMask.crop ? '1' : '0'),
|
|
|
|
'varying vec4 vColor;',
|
|
|
|
'#if hasTexture',
|
|
'varying vec2 vTextureCoord;',
|
|
'uniform sampler2D uSampler;',
|
|
'#if hasCrop',
|
|
'uniform vec4 uCropSource;',
|
|
'#endif',
|
|
'#endif',
|
|
|
|
'void main(void) {',
|
|
'#if hasTexture',
|
|
'#if hasCrop',
|
|
'gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x * uCropSource.z, vTextureCoord.y * uCropSource.w) + uCropSource.xy);',
|
|
'#else',
|
|
'gl_FragColor = texture2D(uSampler, vTextureCoord);',
|
|
'#endif',
|
|
'#else',
|
|
'gl_FragColor = vColor;',
|
|
'#endif',
|
|
'}'
|
|
].join('\n');
|
|
|
|
return fsSource;
|
|
};
|
|
|
|
WebGL2D.prototype.getVertexShaderSource = function getVertexShaderSource(
|
|
stackDepth,
|
|
sMask
|
|
) {
|
|
var w = 2 / this.canvas.width,
|
|
h = -2 / this.canvas.height;
|
|
|
|
stackDepth = stackDepth || 1;
|
|
|
|
var vsSource = [
|
|
'#define hasTexture ' + (sMask & shaderMask.texture ? '1' : '0'),
|
|
'attribute vec4 aVertexPosition;',
|
|
|
|
'#if hasTexture',
|
|
'varying vec2 vTextureCoord;',
|
|
'#endif',
|
|
|
|
'uniform vec4 uColor;',
|
|
'uniform mat3 uTransforms[' + stackDepth + '];',
|
|
|
|
'varying vec4 vColor;',
|
|
|
|
'const mat4 pMatrix = mat4(' +
|
|
w +
|
|
',0,0,0, 0,' +
|
|
h +
|
|
',0,0, 0,0,1.0,1.0, -1.0,1.0,0,0);',
|
|
|
|
'mat3 crunchStack(void) {',
|
|
'mat3 result = uTransforms[0];',
|
|
'for (int i = 1; i < ' + stackDepth + '; ++i) {',
|
|
'result = uTransforms[i] * result;',
|
|
'}',
|
|
'return result;',
|
|
'}',
|
|
|
|
'void main(void) {',
|
|
'vec3 position = crunchStack() * vec3(aVertexPosition.x, aVertexPosition.y, 1.0);',
|
|
'gl_Position = pMatrix * vec4(position, 1.0);',
|
|
'vColor = uColor;',
|
|
'#if hasTexture',
|
|
'vTextureCoord = aVertexPosition.zw;',
|
|
'#endif',
|
|
'}'
|
|
].join('\n');
|
|
return vsSource;
|
|
};
|
|
|
|
// Initialize fragment and vertex shaders
|
|
WebGL2D.prototype.initShaders = function initShaders(
|
|
transformStackDepth,
|
|
sMask
|
|
) {
|
|
var gl = this.gl;
|
|
|
|
transformStackDepth = transformStackDepth || 1;
|
|
sMask = sMask || 0;
|
|
var storedShader = this.shaderPool[transformStackDepth];
|
|
|
|
if (!storedShader) {
|
|
storedShader = this.shaderPool[transformStackDepth] = [];
|
|
}
|
|
storedShader = storedShader[sMask];
|
|
|
|
if (storedShader) {
|
|
gl.useProgram(storedShader);
|
|
this.shaderProgram = storedShader;
|
|
return storedShader;
|
|
} else {
|
|
var fs = (this.fs = gl.createShader(gl.FRAGMENT_SHADER));
|
|
gl.shaderSource(this.fs, this.getFragmentShaderSource(sMask));
|
|
gl.compileShader(this.fs);
|
|
|
|
if (!gl.getShaderParameter(this.fs, gl.COMPILE_STATUS)) {
|
|
throw 'fragment shader error: ' + gl.getShaderInfoLog(this.fs);
|
|
}
|
|
|
|
var vs = (this.vs = gl.createShader(gl.VERTEX_SHADER));
|
|
gl.shaderSource(
|
|
this.vs,
|
|
this.getVertexShaderSource(transformStackDepth, sMask)
|
|
);
|
|
gl.compileShader(this.vs);
|
|
|
|
if (!gl.getShaderParameter(this.vs, gl.COMPILE_STATUS)) {
|
|
throw 'vertex shader error: ' + gl.getShaderInfoLog(this.vs);
|
|
}
|
|
|
|
var shaderProgram = (this.shaderProgram = gl.createProgram());
|
|
shaderProgram.stackDepth = transformStackDepth;
|
|
gl.attachShader(shaderProgram, fs);
|
|
gl.attachShader(shaderProgram, vs);
|
|
gl.linkProgram(shaderProgram);
|
|
|
|
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
|
|
throw 'Could not initialise shaders.';
|
|
}
|
|
|
|
gl.useProgram(shaderProgram);
|
|
|
|
shaderProgram.vertexPositionAttribute = gl.getAttribLocation(
|
|
shaderProgram,
|
|
'aVertexPosition'
|
|
);
|
|
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
|
|
|
|
shaderProgram.uColor = gl.getUniformLocation(shaderProgram, 'uColor');
|
|
shaderProgram.uSampler = gl.getUniformLocation(shaderProgram, 'uSampler');
|
|
shaderProgram.uCropSource = gl.getUniformLocation(
|
|
shaderProgram,
|
|
'uCropSource'
|
|
);
|
|
|
|
shaderProgram.uTransforms = [];
|
|
for (var i = 0; i < transformStackDepth; ++i) {
|
|
shaderProgram.uTransforms[i] = gl.getUniformLocation(
|
|
shaderProgram,
|
|
'uTransforms[' + i + ']'
|
|
);
|
|
} //for
|
|
this.shaderPool[transformStackDepth][sMask] = shaderProgram;
|
|
return shaderProgram;
|
|
} //if
|
|
};
|
|
|
|
var rectVertexPositionBuffer;
|
|
var rectVertexColorBuffer;
|
|
|
|
var pathVertexPositionBuffer;
|
|
var pathVertexColorBuffer;
|
|
|
|
// 2D Vertices and Texture UV coords
|
|
var rectVerts = new Float32Array([
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
1,
|
|
0,
|
|
1,
|
|
1,
|
|
1,
|
|
1,
|
|
1,
|
|
1,
|
|
0,
|
|
1,
|
|
0
|
|
]);
|
|
|
|
WebGL2D.prototype.initBuffers = function initBuffers() {
|
|
var gl = this.gl;
|
|
|
|
rectVertexPositionBuffer = gl.createBuffer();
|
|
rectVertexColorBuffer = gl.createBuffer();
|
|
|
|
pathVertexPositionBuffer = gl.createBuffer();
|
|
pathVertexColorBuffer = gl.createBuffer();
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, rectVertexPositionBuffer);
|
|
gl.bufferData(gl.ARRAY_BUFFER, rectVerts, gl.STATIC_DRAW);
|
|
};
|
|
|
|
// Maintains an array of all WebGL2D instances
|
|
WebGL2D.instances = [];
|
|
|
|
WebGL2D.prototype.postInit = function() {
|
|
WebGL2D.instances.push(this);
|
|
};
|
|
|
|
// Extends gl context with Canvas2D API
|
|
WebGL2D.prototype.initCanvas2DAPI = function initCanvas2DAPI() {
|
|
var gl2d = this,
|
|
gl = this.gl;
|
|
|
|
// Rendering Canvas for text fonts
|
|
var textCanvas = document.createElement('canvas');
|
|
textCanvas.width = gl2d.canvas.width;
|
|
textCanvas.height = gl2d.canvas.height;
|
|
var textCtx = textCanvas.getContext('2d');
|
|
|
|
var reRGBAColor = /^rgb(a)?\(\s*(-?[\d]+)(%)?\s*,\s*(-?[\d]+)(%)?\s*,\s*(-?[\d]+)(%)?\s*,?\s*(-?[\d\.]+)?\s*\)$/;
|
|
var reHSLAColor = /^hsl(a)?\(\s*(-?[\d\.]+)\s*,\s*(-?[\d\.]+)%\s*,\s*(-?[\d\.]+)%\s*,?\s*(-?[\d\.]+)?\s*\)$/;
|
|
var reHex6Color = /^#([0-9A-Fa-f]{6})$/;
|
|
var reHex3Color = /^#([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])$/;
|
|
|
|
function HSLAToRGBA(h, s, l, a) {
|
|
var r, g, b, m1, m2;
|
|
|
|
// Clamp and Normalize values
|
|
h = ((h % 360 + 360) % 360) / 360;
|
|
s = s > 100 ? 1 : s / 100;
|
|
s = s < 0 ? 0 : s;
|
|
l = l > 100 ? 1 : l / 100;
|
|
l = l < 0 ? 0 : l;
|
|
|
|
m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
|
|
m1 = l * 2 - m2;
|
|
|
|
function getHue(value) {
|
|
var hue;
|
|
|
|
if (value * 6 < 1) {
|
|
hue = m1 + (m2 - m1) * value * 6;
|
|
} else if (value * 2 < 1) {
|
|
hue = m2;
|
|
} else if (value * 3 < 2) {
|
|
hue = m1 + (m2 - m1) * (2 / 3 - value) * 6;
|
|
} else {
|
|
hue = m1;
|
|
}
|
|
|
|
return hue;
|
|
}
|
|
|
|
r = getHue(h + 1 / 3);
|
|
g = getHue(h);
|
|
b = getHue(h - 1 / 3);
|
|
|
|
return [r, g, b, a];
|
|
}
|
|
|
|
// Converts rgb(a) color string to gl color vector
|
|
function colorStringToVec4(value) {
|
|
var result = [],
|
|
match,
|
|
channel,
|
|
isPercent,
|
|
hasAlpha,
|
|
alphaChannel,
|
|
sameType;
|
|
|
|
if ((match = reRGBAColor.exec(value))) {
|
|
(hasAlpha = match[1]), (alphaChannel = parseFloat(match[8]));
|
|
|
|
if (
|
|
(hasAlpha && isNaN(alphaChannel)) ||
|
|
(!hasAlpha && !isNaN(alphaChannel))
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
sameType = match[3];
|
|
|
|
for (var i = 2; i < 8; i += 2) {
|
|
(channel = match[i]), (isPercent = match[i + 1]);
|
|
|
|
if (isPercent !== sameType) {
|
|
return false;
|
|
}
|
|
|
|
// Clamp and normalize values
|
|
if (isPercent) {
|
|
channel = channel > 100 ? 1 : channel / 100;
|
|
channel = channel < 0 ? 0 : channel;
|
|
} else {
|
|
channel = channel > 255 ? 1 : channel / 255;
|
|
channel = channel < 0 ? 0 : channel;
|
|
}
|
|
|
|
result.push(channel);
|
|
}
|
|
|
|
result.push(hasAlpha ? alphaChannel : 1.0);
|
|
} else if ((match = reHSLAColor.exec(value))) {
|
|
(hasAlpha = match[1]), (alphaChannel = parseFloat(match[5]));
|
|
result = HSLAToRGBA(
|
|
match[2],
|
|
match[3],
|
|
match[4],
|
|
parseFloat(hasAlpha && alphaChannel ? alphaChannel : 1.0)
|
|
);
|
|
} else if ((match = reHex6Color.exec(value))) {
|
|
var colorInt = parseInt(match[1], 16);
|
|
result = [
|
|
((colorInt & 0xff0000) >> 16) / 255,
|
|
((colorInt & 0x00ff00) >> 8) / 255,
|
|
(colorInt & 0x0000ff) / 255,
|
|
1.0
|
|
];
|
|
} else if ((match = reHex3Color.exec(value))) {
|
|
var hexString =
|
|
'#' +
|
|
[match[1], match[1], match[2], match[2], match[3], match[3]].join('');
|
|
result = colorStringToVec4(hexString);
|
|
} else if (value.toLowerCase() in colorKeywords) {
|
|
result = colorStringToVec4(colorKeywords[value.toLowerCase()]);
|
|
} else if (value.toLowerCase() === 'transparent') {
|
|
result = [0, 0, 0, 0];
|
|
} else {
|
|
// Color keywords not yet implemented, ie "orange", return hot pink
|
|
return false;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function colorVecToString(vec4) {
|
|
return (
|
|
'rgba(' +
|
|
vec4[0] * 255 +
|
|
', ' +
|
|
vec4[1] * 255 +
|
|
', ' +
|
|
vec4[2] * 255 +
|
|
', ' +
|
|
parseFloat(vec4[3]) +
|
|
')'
|
|
);
|
|
}
|
|
|
|
var colorKeywords = {
|
|
aliceblue: '#f0f8ff',
|
|
antiquewhite: '#faebd7',
|
|
aqua: '#00ffff',
|
|
aquamarine: '#7fffd4',
|
|
azure: '#f0ffff',
|
|
beige: '#f5f5dc',
|
|
bisque: '#ffe4c4',
|
|
black: '#000000',
|
|
blanchedalmond: '#ffebcd',
|
|
blue: '#0000ff',
|
|
blueviolet: '#8a2be2',
|
|
brown: '#a52a2a',
|
|
burlywood: '#deb887',
|
|
cadetblue: '#5f9ea0',
|
|
chartreuse: '#7fff00',
|
|
chocolate: '#d2691e',
|
|
coral: '#ff7f50',
|
|
cornflowerblue: '#6495ed',
|
|
cornsilk: '#fff8dc',
|
|
crimson: '#dc143c',
|
|
cyan: '#00ffff',
|
|
darkblue: '#00008b',
|
|
darkcyan: '#008b8b',
|
|
darkgoldenrod: '#b8860b',
|
|
darkgray: '#a9a9a9',
|
|
darkgreen: '#006400',
|
|
darkkhaki: '#bdb76b',
|
|
darkmagenta: '#8b008b',
|
|
darkolivegreen: '#556b2f',
|
|
darkorange: '#ff8c00',
|
|
darkorchid: '#9932cc',
|
|
darkred: '#8b0000',
|
|
darksalmon: '#e9967a',
|
|
darkseagreen: '#8fbc8f',
|
|
darkslateblue: '#483d8b',
|
|
darkslategray: '#2f4f4f',
|
|
darkturquoise: '#00ced1',
|
|
darkviolet: '#9400d3',
|
|
deeppink: '#ff1493',
|
|
deepskyblue: '#00bfff',
|
|
dimgray: '#696969',
|
|
dodgerblue: '#1e90ff',
|
|
firebrick: '#b22222',
|
|
floralwhite: '#fffaf0',
|
|
forestgreen: '#228b22',
|
|
fuchsia: '#ff00ff',
|
|
gainsboro: '#dcdcdc',
|
|
ghostwhite: '#f8f8ff',
|
|
gold: '#ffd700',
|
|
goldenrod: '#daa520',
|
|
gray: '#808080',
|
|
green: '#008000',
|
|
greenyellow: '#adff2f',
|
|
grey: '#808080',
|
|
honeydew: '#f0fff0',
|
|
hotpink: '#ff69b4',
|
|
indianred: '#cd5c5c',
|
|
indigo: '#4b0082',
|
|
ivory: '#fffff0',
|
|
khaki: '#f0e68c',
|
|
lavender: '#e6e6fa',
|
|
lavenderblush: '#fff0f5',
|
|
lawngreen: '#7cfc00',
|
|
lemonchiffon: '#fffacd',
|
|
lightblue: '#add8e6',
|
|
lightcoral: '#f08080',
|
|
lightcyan: '#e0ffff',
|
|
lightgoldenrodyellow: '#fafad2',
|
|
lightgrey: '#d3d3d3',
|
|
lightgreen: '#90ee90',
|
|
lightpink: '#ffb6c1',
|
|
lightsalmon: '#ffa07a',
|
|
lightseagreen: '#20b2aa',
|
|
lightskyblue: '#87cefa',
|
|
lightslategray: '#778899',
|
|
lightsteelblue: '#b0c4de',
|
|
lightyellow: '#ffffe0',
|
|
lime: '#00ff00',
|
|
limegreen: '#32cd32',
|
|
linen: '#faf0e6',
|
|
magenta: '#ff00ff',
|
|
maroon: '#800000',
|
|
mediumaquamarine: '#66cdaa',
|
|
mediumblue: '#0000cd',
|
|
mediumorchid: '#ba55d3',
|
|
mediumpurple: '#9370d8',
|
|
mediumseagreen: '#3cb371',
|
|
mediumslateblue: '#7b68ee',
|
|
mediumspringgreen: '#00fa9a',
|
|
mediumturquoise: '#48d1cc',
|
|
mediumvioletred: '#c71585',
|
|
midnightblue: '#191970',
|
|
mintcream: '#f5fffa',
|
|
mistyrose: '#ffe4e1',
|
|
moccasin: '#ffe4b5',
|
|
navajowhite: '#ffdead',
|
|
navy: '#000080',
|
|
oldlace: '#fdf5e6',
|
|
olive: '#808000',
|
|
olivedrab: '#6b8e23',
|
|
orange: '#ffa500',
|
|
orangered: '#ff4500',
|
|
orchid: '#da70d6',
|
|
palegoldenrod: '#eee8aa',
|
|
palegreen: '#98fb98',
|
|
paleturquoise: '#afeeee',
|
|
palevioletred: '#d87093',
|
|
papayawhip: '#ffefd5',
|
|
peachpuff: '#ffdab9',
|
|
peru: '#cd853f',
|
|
pink: '#ffc0cb',
|
|
plum: '#dda0dd',
|
|
powderblue: '#b0e0e6',
|
|
purple: '#800080',
|
|
red: '#ff0000',
|
|
rosybrown: '#bc8f8f',
|
|
royalblue: '#4169e1',
|
|
saddlebrown: '#8b4513',
|
|
salmon: '#fa8072',
|
|
sandybrown: '#f4a460',
|
|
seagreen: '#2e8b57',
|
|
seashell: '#fff5ee',
|
|
sienna: '#a0522d',
|
|
silver: '#c0c0c0',
|
|
skyblue: '#87ceeb',
|
|
slateblue: '#6a5acd',
|
|
slategray: '#708090',
|
|
snow: '#fffafa',
|
|
springgreen: '#00ff7f',
|
|
steelblue: '#4682b4',
|
|
tan: '#d2b48c',
|
|
teal: '#008080',
|
|
thistle: '#d8bfd8',
|
|
tomato: '#ff6347',
|
|
turquoise: '#40e0d0',
|
|
violet: '#ee82ee',
|
|
wheat: '#f5deb3',
|
|
white: '#ffffff',
|
|
whitesmoke: '#f5f5f5',
|
|
yellow: '#ffff00',
|
|
yellowgreen: '#9acd32'
|
|
};
|
|
|
|
// Maintain drawing state params during gl.save and gl.restore. see saveDrawState() and restoreDrawState()
|
|
var drawState = {},
|
|
drawStateStack = [];
|
|
|
|
// A fast simple shallow clone
|
|
function cloneObject(obj) {
|
|
var target = {};
|
|
for (var i in obj) {
|
|
if (obj.hasOwnProperty(i)) {
|
|
target[i] = obj[i];
|
|
}
|
|
}
|
|
return target;
|
|
}
|
|
|
|
function saveDrawState() {
|
|
var bakedDrawState = {
|
|
fillStyle: [
|
|
drawState.fillStyle[0],
|
|
drawState.fillStyle[1],
|
|
drawState.fillStyle[2],
|
|
drawState.fillStyle[3]
|
|
],
|
|
strokeStyle: [
|
|
drawState.strokeStyle[0],
|
|
drawState.strokeStyle[1],
|
|
drawState.strokeStyle[2],
|
|
drawState.strokeStyle[3]
|
|
],
|
|
globalAlpha: drawState.globalAlpha,
|
|
globalCompositeOperation: drawState.globalCompositeOperation,
|
|
lineCap: drawState.lineCap,
|
|
lineJoin: drawState.lineJoin,
|
|
lineWidth: drawState.lineWidth,
|
|
miterLimit: drawState.miterLimit,
|
|
shadowColor: drawState.shadowColor,
|
|
shadowBlur: drawState.shadowBlur,
|
|
shadowOffsetX: drawState.shadowOffsetX,
|
|
shadowOffsetY: drawState.shadowOffsetY,
|
|
textAlign: drawState.textAlign,
|
|
font: drawState.font,
|
|
textBaseline: drawState.textBaseline
|
|
};
|
|
|
|
drawStateStack.push(bakedDrawState);
|
|
}
|
|
|
|
function restoreDrawState() {
|
|
if (drawStateStack.length) {
|
|
drawState = drawStateStack.pop();
|
|
}
|
|
}
|
|
|
|
// WebGL requires colors as a vector while Canvas2D sets colors as an rgba string
|
|
// These getters and setters store the original rgba string as well as convert to a vector
|
|
drawState.fillStyle = [0, 0, 0, 1]; // default black
|
|
|
|
Object.defineProperty(gl, 'fillStyle', {
|
|
get: function() {
|
|
return colorVecToString(drawState.fillStyle);
|
|
},
|
|
set: function(value) {
|
|
drawState.fillStyle = colorStringToVec4(value) || drawState.fillStyle;
|
|
}
|
|
});
|
|
|
|
drawState.strokeStyle = [0, 0, 0, 1]; // default black
|
|
|
|
Object.defineProperty(gl, 'strokeStyle', {
|
|
get: function() {
|
|
return colorVecToString(drawState.strokeStyle);
|
|
},
|
|
set: function(value) {
|
|
drawState.strokeStyle =
|
|
colorStringToVec4(value) || drawStyle.strokeStyle;
|
|
}
|
|
});
|
|
|
|
// WebGL already has a lineWidth() function but Canvas2D requires a lineWidth property
|
|
// Store the original lineWidth() function for later use
|
|
gl.$lineWidth = gl.lineWidth;
|
|
drawState.lineWidth = 1.0;
|
|
|
|
Object.defineProperty(gl, 'lineWidth', {
|
|
get: function() {
|
|
return drawState.lineWidth;
|
|
},
|
|
set: function(value) {
|
|
gl.$lineWidth(value);
|
|
drawState.lineWidth = value;
|
|
}
|
|
});
|
|
|
|
// Currently unsupported attributes and their default values
|
|
drawState.lineCap = 'butt';
|
|
|
|
Object.defineProperty(gl, 'lineCap', {
|
|
get: function() {
|
|
return drawState.lineCap;
|
|
},
|
|
set: function(value) {
|
|
drawState.lineCap = value;
|
|
}
|
|
});
|
|
|
|
drawState.lineJoin = 'miter';
|
|
|
|
Object.defineProperty(gl, 'lineJoin', {
|
|
get: function() {
|
|
return drawState.lineJoin;
|
|
},
|
|
set: function(value) {
|
|
drawState.lineJoin = value;
|
|
}
|
|
});
|
|
|
|
drawState.miterLimit = 10;
|
|
|
|
Object.defineProperty(gl, 'miterLimit', {
|
|
get: function() {
|
|
return drawState.miterLimit;
|
|
},
|
|
set: function(value) {
|
|
drawState.miterLimit = value;
|
|
}
|
|
});
|
|
|
|
drawState.shadowOffsetX = 0;
|
|
|
|
Object.defineProperty(gl, 'shadowOffsetX', {
|
|
get: function() {
|
|
return drawState.shadowOffsetX;
|
|
},
|
|
set: function(value) {
|
|
drawState.shadowOffsetX = value;
|
|
}
|
|
});
|
|
|
|
drawState.shadowOffsetY = 0;
|
|
|
|
Object.defineProperty(gl, 'shadowOffsetY', {
|
|
get: function() {
|
|
return drawState.shadowOffsetY;
|
|
},
|
|
set: function(value) {
|
|
drawState.shadowOffsetY = value;
|
|
}
|
|
});
|
|
|
|
drawState.shadowBlur = 0;
|
|
|
|
Object.defineProperty(gl, 'shadowBlur', {
|
|
get: function() {
|
|
return drawState.shadowBlur;
|
|
},
|
|
set: function(value) {
|
|
drawState.shadowBlur = value;
|
|
}
|
|
});
|
|
|
|
drawState.shadowColor = 'rgba(0, 0, 0, 0.0)';
|
|
|
|
Object.defineProperty(gl, 'shadowColor', {
|
|
get: function() {
|
|
return drawState.shadowColor;
|
|
},
|
|
set: function(value) {
|
|
drawState.shadowColor = value;
|
|
}
|
|
});
|
|
|
|
drawState.font = '10px sans-serif';
|
|
|
|
Object.defineProperty(gl, 'font', {
|
|
get: function() {
|
|
return drawState.font;
|
|
},
|
|
set: function(value) {
|
|
textCtx.font = value;
|
|
drawState.font = value;
|
|
}
|
|
});
|
|
|
|
drawState.textAlign = 'start';
|
|
|
|
Object.defineProperty(gl, 'textAlign', {
|
|
get: function() {
|
|
return drawState.textAlign;
|
|
},
|
|
set: function(value) {
|
|
drawState.textAlign = value;
|
|
}
|
|
});
|
|
|
|
drawState.textBaseline = 'alphabetic';
|
|
|
|
Object.defineProperty(gl, 'textBaseline', {
|
|
get: function() {
|
|
return drawState.textBaseline;
|
|
},
|
|
set: function(value) {
|
|
drawState.textBaseline = value;
|
|
}
|
|
});
|
|
|
|
// This attribute will need to control global alpha of objects drawn.
|
|
drawState.globalAlpha = 1.0;
|
|
|
|
Object.defineProperty(gl, 'globalAlpha', {
|
|
get: function() {
|
|
return drawState.globalAlpha;
|
|
},
|
|
set: function(value) {
|
|
drawState.globalAlpha = value;
|
|
}
|
|
});
|
|
|
|
// This attribute will need to set the gl.blendFunc mode
|
|
drawState.globalCompositeOperation = 'source-over';
|
|
|
|
Object.defineProperty(gl, 'globalCompositeOperation', {
|
|
get: function() {
|
|
return drawState.globalCompositeOperation;
|
|
},
|
|
set: function(value) {
|
|
drawState.globalCompositeOperation = value;
|
|
}
|
|
});
|
|
|
|
// Need a solution for drawing text that isnt stupid slow
|
|
gl.fillText = function fillText(text, x, y) {
|
|
/*
|
|
textCtx.clearRect(0, 0, gl2d.canvas.width, gl2d.canvas.height);
|
|
textCtx.fillStyle = gl.fillStyle;
|
|
textCtx.fillText(text, x, y);
|
|
|
|
gl.drawImage(textCanvas, 0, 0);
|
|
*/
|
|
};
|
|
|
|
gl.strokeText = function strokeText() {};
|
|
|
|
gl.measureText = function measureText() {
|
|
return 1;
|
|
};
|
|
|
|
var tempCanvas = document.createElement('canvas');
|
|
var tempCtx = tempCanvas.getContext('2d');
|
|
|
|
gl.save = function save() {
|
|
gl2d.transform.pushMatrix();
|
|
saveDrawState();
|
|
};
|
|
|
|
gl.restore = function restore() {
|
|
gl2d.transform.popMatrix();
|
|
restoreDrawState();
|
|
};
|
|
|
|
gl.translate = function translate(x, y) {
|
|
gl2d.transform.translate(x, y);
|
|
};
|
|
|
|
gl.rotate = function rotate(a) {
|
|
gl2d.transform.rotate(a);
|
|
};
|
|
|
|
gl.scale = function scale(x, y) {
|
|
gl2d.transform.scale(x, y);
|
|
};
|
|
|
|
gl.createImageData = function createImageData(width, height) {
|
|
return tempCtx.createImageData(width, height);
|
|
};
|
|
|
|
gl.getImageData = function getImageData(x, y, width, height) {
|
|
var data = tempCtx.createImageData(width, height);
|
|
var buffer = new Uint8Array(width * height * 4);
|
|
gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer);
|
|
var w = width * 4,
|
|
h = height;
|
|
for (var i = 0, maxI = h / 2; i < maxI; ++i) {
|
|
for (var j = 0, maxJ = w; j < maxJ; ++j) {
|
|
var index1 = i * w + j;
|
|
var index2 = (h - i - 1) * w + j;
|
|
data.data[index1] = buffer[index2];
|
|
data.data[index2] = buffer[index1];
|
|
} //for
|
|
} //for
|
|
|
|
return data;
|
|
};
|
|
|
|
gl.putImageData = function putImageData(imageData, x, y) {
|
|
gl.drawImage(imageData, x, y);
|
|
};
|
|
|
|
gl.transform = function transform(m11, m12, m21, m22, dx, dy) {
|
|
var m = gl2d.transform.m_stack[gl2d.transform.c_stack];
|
|
|
|
m[0] *= m11;
|
|
m[1] *= m21;
|
|
m[2] *= dx;
|
|
m[3] *= m12;
|
|
m[4] *= m22;
|
|
m[5] *= dy;
|
|
m[6] = 0;
|
|
m[7] = 0;
|
|
};
|
|
|
|
function sendTransformStack(sp) {
|
|
var stack = gl2d.transform.m_stack;
|
|
for (var i = 0, maxI = gl2d.transform.c_stack + 1; i < maxI; ++i) {
|
|
gl.uniformMatrix3fv(sp.uTransforms[i], false, stack[maxI - 1 - i]);
|
|
} //for
|
|
}
|
|
|
|
gl.setTransform = function setTransform(m11, m12, m21, m22, dx, dy) {
|
|
gl2d.transform.setIdentity();
|
|
gl.transform.apply(this, arguments);
|
|
};
|
|
|
|
gl.fillRect = function fillRect(x, y, width, height) {
|
|
var transform = gl2d.transform;
|
|
var shaderProgram = gl2d.initShaders(transform.c_stack + 2, 0);
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, rectVertexPositionBuffer);
|
|
gl.vertexAttribPointer(
|
|
shaderProgram.vertexPositionAttribute,
|
|
4,
|
|
gl.FLOAT,
|
|
false,
|
|
0,
|
|
0
|
|
);
|
|
|
|
transform.pushMatrix();
|
|
|
|
transform.translate(x, y);
|
|
transform.scale(width, height);
|
|
|
|
sendTransformStack(shaderProgram);
|
|
|
|
gl.uniform4f(
|
|
shaderProgram.uColor,
|
|
drawState.fillStyle[0],
|
|
drawState.fillStyle[1],
|
|
drawState.fillStyle[2],
|
|
drawState.fillStyle[3]
|
|
);
|
|
|
|
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
|
|
|
|
transform.popMatrix();
|
|
};
|
|
|
|
gl.strokeRect = function strokeRect(x, y, width, height) {
|
|
var transform = gl2d.transform;
|
|
var shaderProgram = gl2d.initShaders(transform.c_stack + 2, 0);
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, rectVertexPositionBuffer);
|
|
gl.vertexAttribPointer(
|
|
shaderProgram.vertexPositionAttribute,
|
|
4,
|
|
gl.FLOAT,
|
|
false,
|
|
0,
|
|
0
|
|
);
|
|
|
|
transform.pushMatrix();
|
|
|
|
transform.translate(x, y);
|
|
transform.scale(width, height);
|
|
|
|
sendTransformStack(shaderProgram);
|
|
|
|
gl.uniform4f(
|
|
shaderProgram.uColor,
|
|
drawState.strokeStyle[0],
|
|
drawState.strokeStyle[1],
|
|
drawState.strokeStyle[2],
|
|
drawState.strokeStyle[3]
|
|
);
|
|
|
|
gl.drawArrays(gl.LINE_LOOP, 0, 4);
|
|
|
|
transform.popMatrix();
|
|
};
|
|
|
|
gl.clearRect = function clearRect(x, y, width, height) {};
|
|
|
|
var subPaths = [];
|
|
|
|
function SubPath(x, y) {
|
|
this.closed = false;
|
|
this.verts = [x, y, 0, 0];
|
|
}
|
|
|
|
// Empty the list of subpaths so that the context once again has zero subpaths
|
|
gl.beginPath = function beginPath() {
|
|
subPaths.length = 0;
|
|
};
|
|
|
|
// Mark last subpath as closed and create a new subpath with the same starting point as the previous subpath
|
|
gl.closePath = function closePath() {
|
|
if (subPaths.length) {
|
|
// Mark last subpath closed.
|
|
var prevPath = subPaths[subPaths.length - 1],
|
|
startX = prevPath.verts[0],
|
|
startY = prevPath.verts[1];
|
|
prevPath.closed = true;
|
|
|
|
// Create new subpath using the starting position of previous subpath
|
|
var newPath = new SubPath(startX, startY);
|
|
subPaths.push(newPath);
|
|
}
|
|
};
|
|
|
|
// Create a new subpath with the specified point as its first (and only) point
|
|
gl.moveTo = function moveTo(x, y) {
|
|
subPaths.push(new SubPath(x, y));
|
|
};
|
|
|
|
gl.lineTo = function lineTo(x, y) {
|
|
if (subPaths.length) {
|
|
subPaths[subPaths.length - 1].verts.push(x, y, 0, 0);
|
|
} else {
|
|
// Create a new subpath if none currently exist
|
|
gl.moveTo(x, y);
|
|
}
|
|
};
|
|
|
|
gl.quadraticCurveTo = function quadraticCurveTo(cp1x, cp1y, x, y) {};
|
|
|
|
gl.bezierCurveTo = function bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {};
|
|
|
|
gl.arcTo = function arcTo() {};
|
|
|
|
// Adds a closed rect subpath and creates a new subpath
|
|
gl.rect = function rect(x, y, w, h) {
|
|
gl.moveTo(x, y);
|
|
gl.lineTo(x + w, y);
|
|
gl.lineTo(x + w, y + h);
|
|
gl.lineTo(x, y + h);
|
|
gl.closePath();
|
|
};
|
|
|
|
gl.arc = function arc(x, y, radius, startAngle, endAngle, anticlockwise) {};
|
|
|
|
function fillSubPath(index) {
|
|
var transform = gl2d.transform;
|
|
var shaderProgram = gl2d.initShaders(transform.c_stack + 2, 0);
|
|
|
|
var subPath = subPaths[index];
|
|
var verts = subPath.verts;
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, pathVertexPositionBuffer);
|
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);
|
|
|
|
gl.vertexAttribPointer(
|
|
shaderProgram.vertexPositionAttribute,
|
|
4,
|
|
gl.FLOAT,
|
|
false,
|
|
0,
|
|
0
|
|
);
|
|
|
|
transform.pushMatrix();
|
|
|
|
sendTransformStack(shaderProgram);
|
|
|
|
gl.uniform4f(
|
|
shaderProgram.uColor,
|
|
drawState.fillStyle[0],
|
|
drawState.fillStyle[1],
|
|
drawState.fillStyle[2],
|
|
drawState.fillStyle[3]
|
|
);
|
|
|
|
gl.drawArrays(gl.TRIANGLE_FAN, 0, verts.length / 4);
|
|
|
|
transform.popMatrix();
|
|
}
|
|
|
|
gl.fill = function fill() {
|
|
for (var i = 0; i < subPaths.length; i++) {
|
|
fillSubPath(i);
|
|
}
|
|
};
|
|
|
|
function strokeSubPath(index) {
|
|
var transform = gl2d.transform;
|
|
var shaderProgram = gl2d.initShaders(transform.c_stack + 2, 0);
|
|
|
|
var subPath = subPaths[index];
|
|
var verts = subPath.verts;
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, pathVertexPositionBuffer);
|
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);
|
|
|
|
gl.vertexAttribPointer(
|
|
shaderProgram.vertexPositionAttribute,
|
|
4,
|
|
gl.FLOAT,
|
|
false,
|
|
0,
|
|
0
|
|
);
|
|
|
|
transform.pushMatrix();
|
|
|
|
sendTransformStack(shaderProgram);
|
|
|
|
gl.uniform4f(
|
|
shaderProgram.uColor,
|
|
drawState.strokeStyle[0],
|
|
drawState.strokeStyle[1],
|
|
drawState.strokeStyle[2],
|
|
drawState.strokeStyle[3]
|
|
);
|
|
|
|
if (subPath.closed) {
|
|
gl.drawArrays(gl.LINE_LOOP, 0, verts.length / 4);
|
|
} else {
|
|
gl.drawArrays(gl.LINE_STRIP, 0, verts.length / 4);
|
|
}
|
|
|
|
transform.popMatrix();
|
|
}
|
|
|
|
gl.stroke = function stroke() {
|
|
for (var i = 0; i < subPaths.length; i++) {
|
|
strokeSubPath(i);
|
|
}
|
|
};
|
|
|
|
gl.clip = function clip() {};
|
|
|
|
gl.isPointInPath = function isPointInPath() {};
|
|
|
|
gl.drawFocusRing = function drawFocusRing() {};
|
|
|
|
var imageCache = [],
|
|
textureCache = [];
|
|
|
|
function Texture(image) {
|
|
this.obj = gl.createTexture();
|
|
this.index = textureCache.push(this);
|
|
|
|
imageCache.push(image);
|
|
|
|
// we may wish to consider tiling large images like this instead of scaling and
|
|
// adjust appropriately (flip to next texture source and tile offset) when drawing
|
|
if (
|
|
image.width > gl2d.maxTextureSize ||
|
|
image.height > gl2d.maxTextureSize
|
|
) {
|
|
var canvas = document.createElement('canvas');
|
|
|
|
canvas.width =
|
|
image.width > gl2d.maxTextureSize ? gl2d.maxTextureSize : image.width;
|
|
canvas.height =
|
|
image.height > gl2d.maxTextureSize
|
|
? gl2d.maxTextureSize
|
|
: image.height;
|
|
|
|
var ctx = canvas.getContext('2d');
|
|
|
|
ctx.drawImage(
|
|
image,
|
|
0,
|
|
0,
|
|
image.width,
|
|
image.height,
|
|
0,
|
|
0,
|
|
canvas.width,
|
|
canvas.height
|
|
);
|
|
|
|
image = canvas;
|
|
}
|
|
|
|
gl.bindTexture(gl.TEXTURE_2D, this.obj);
|
|
gl.texImage2D(
|
|
gl.TEXTURE_2D,
|
|
0,
|
|
gl.RGBA,
|
|
gl.RGBA,
|
|
gl.UNSIGNED_BYTE,
|
|
image
|
|
);
|
|
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);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
|
|
// Enable Mip mapping on power-of-2 textures
|
|
if (isPOT(image.width) && isPOT(image.height)) {
|
|
gl.texParameteri(
|
|
gl.TEXTURE_2D,
|
|
gl.TEXTURE_MIN_FILTER,
|
|
gl.LINEAR_MIPMAP_LINEAR
|
|
);
|
|
gl.generateMipmap(gl.TEXTURE_2D);
|
|
} else {
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
}
|
|
|
|
// Unbind texture
|
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
}
|
|
|
|
gl.drawImage = function drawImage(image, a, b, c, d, e, f, g, h) {
|
|
var transform = gl2d.transform;
|
|
|
|
transform.pushMatrix();
|
|
|
|
var sMask = shaderMask.texture;
|
|
var doCrop = false;
|
|
|
|
//drawImage(image, dx, dy)
|
|
if (arguments.length === 3) {
|
|
transform.translate(a, b);
|
|
transform.scale(image.width, image.height);
|
|
} else if (arguments.length === 5) {
|
|
//drawImage(image, dx, dy, dw, dh)
|
|
transform.translate(a, b);
|
|
transform.scale(c, d);
|
|
} else if (arguments.length === 9) {
|
|
//drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
|
|
transform.translate(e, f);
|
|
transform.scale(g, h);
|
|
sMask = sMask | shaderMask.crop;
|
|
doCrop = true;
|
|
}
|
|
|
|
var shaderProgram = gl2d.initShaders(transform.c_stack, sMask);
|
|
|
|
var texture,
|
|
cacheIndex = imageCache.indexOf(image);
|
|
|
|
if (cacheIndex !== -1) {
|
|
texture = textureCache[cacheIndex];
|
|
} else {
|
|
texture = new Texture(image);
|
|
}
|
|
|
|
if (doCrop) {
|
|
gl.uniform4f(
|
|
shaderProgram.uCropSource,
|
|
a / image.width,
|
|
b / image.height,
|
|
c / image.width,
|
|
d / image.height
|
|
);
|
|
}
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, rectVertexPositionBuffer);
|
|
gl.vertexAttribPointer(
|
|
shaderProgram.vertexPositionAttribute,
|
|
4,
|
|
gl.FLOAT,
|
|
false,
|
|
0,
|
|
0
|
|
);
|
|
|
|
gl.bindTexture(gl.TEXTURE_2D, texture.obj);
|
|
gl.activeTexture(gl.TEXTURE0);
|
|
|
|
gl.uniform1i(shaderProgram.uSampler, 0);
|
|
|
|
sendTransformStack(shaderProgram);
|
|
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
|
|
|
|
transform.popMatrix();
|
|
};
|
|
};
|
|
})(Math);
|