/** * WebGL-2D.js - HTML5 Canvas2D API in a WebGL context * * Created by Corban Brook on 2011-03-02. * Amended to by Bobby Richter on 2011-03-03 * CubicVR.js by Charles Cliffe 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);