blob: 1bca8a3d878f77fae3e7b592717f4ad0ed8a1c4b [file] [log] [blame]
//
// Copyright (c) 2015 Paperspace Co. All rights reserved.
//
// 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.
//
// universal module definition
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.YUVCanvas = factory();
}
}(this, function () {
/**
* This class can be used to render output pictures from an H264bsdDecoder to a canvas element.
* If available the content is rendered using WebGL.
*/
function YUVCanvas(parOptions) {
parOptions = parOptions || {};
this.canvasElement = parOptions.canvas || document.createElement("canvas");
this.contextOptions = parOptions.contextOptions;
this.type = parOptions.type || "yuv420";
this.customYUV444 = parOptions.customYUV444;
this.conversionType = parOptions.conversionType || "rec601";
this.width = parOptions.width || 640;
this.height = parOptions.height || 320;
this.animationTime = parOptions.animationTime || 0;
this.canvasElement.width = this.width;
this.canvasElement.height = this.height;
this.initContextGL();
if(this.contextGL) {
this.initProgram();
this.initBuffers();
this.initTextures();
};
/**
* Draw the next output picture using WebGL
*/
if (this.type === "yuv420"){
this.drawNextOuptutPictureGL = function(par) {
var gl = this.contextGL;
var texturePosBuffer = this.texturePosBuffer;
var uTexturePosBuffer = this.uTexturePosBuffer;
var vTexturePosBuffer = this.vTexturePosBuffer;
var yTextureRef = this.yTextureRef;
var uTextureRef = this.uTextureRef;
var vTextureRef = this.vTextureRef;
var yData = par.yData;
var uData = par.uData;
var vData = par.vData;
var width = this.width;
var height = this.height;
var yDataPerRow = par.yDataPerRow || width;
var yRowCnt = par.yRowCnt || height;
var uDataPerRow = par.uDataPerRow || (width / 2);
var uRowCnt = par.uRowCnt || (height / 2);
var vDataPerRow = par.vDataPerRow || uDataPerRow;
var vRowCnt = par.vRowCnt || uRowCnt;
gl.viewport(0, 0, width, height);
var tTop = 0;
var tLeft = 0;
var tBottom = height / yRowCnt;
var tRight = width / yDataPerRow;
var texturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);
gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
gl.bufferData(gl.ARRAY_BUFFER, texturePosValues, gl.DYNAMIC_DRAW);
if (this.customYUV444){
tBottom = height / uRowCnt;
tRight = width / uDataPerRow;
}else{
tBottom = (height / 2) / uRowCnt;
tRight = (width / 2) / uDataPerRow;
};
var uTexturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);
gl.bindBuffer(gl.ARRAY_BUFFER, uTexturePosBuffer);
gl.bufferData(gl.ARRAY_BUFFER, uTexturePosValues, gl.DYNAMIC_DRAW);
if (this.customYUV444){
tBottom = height / vRowCnt;
tRight = width / vDataPerRow;
}else{
tBottom = (height / 2) / vRowCnt;
tRight = (width / 2) / vDataPerRow;
};
var vTexturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);
gl.bindBuffer(gl.ARRAY_BUFFER, vTexturePosBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vTexturePosValues, gl.DYNAMIC_DRAW);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, yTextureRef);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, yDataPerRow, yRowCnt, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, yData);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, uTextureRef);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, uDataPerRow, uRowCnt, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, uData);
gl.activeTexture(gl.TEXTURE2);
gl.bindTexture(gl.TEXTURE_2D, vTextureRef);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, vDataPerRow, vRowCnt, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, vData);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
};
}else if (this.type === "yuv422"){
this.drawNextOuptutPictureGL = function(par) {
var gl = this.contextGL;
var texturePosBuffer = this.texturePosBuffer;
var textureRef = this.textureRef;
var data = par.data;
var width = this.width;
var height = this.height;
var dataPerRow = par.dataPerRow || (width * 2);
var rowCnt = par.rowCnt || height;
gl.viewport(0, 0, width, height);
var tTop = 0;
var tLeft = 0;
var tBottom = height / rowCnt;
var tRight = width / (dataPerRow / 2);
var texturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);
gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
gl.bufferData(gl.ARRAY_BUFFER, texturePosValues, gl.DYNAMIC_DRAW);
gl.uniform2f(gl.getUniformLocation(this.shaderProgram, 'resolution'), dataPerRow, height);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, textureRef);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, dataPerRow, rowCnt, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
};
};
};
/**
* Returns true if the canvas supports WebGL
*/
YUVCanvas.prototype.isWebGL = function() {
return this.contextGL;
};
/**
* Create the GL context from the canvas element
*/
YUVCanvas.prototype.initContextGL = function() {
var canvas = this.canvasElement;
var gl = null;
var validContextNames = ["webgl", "experimental-webgl", "moz-webgl", "webkit-3d"];
var nameIndex = 0;
while(!gl && nameIndex < validContextNames.length) {
var contextName = validContextNames[nameIndex];
try {
if (this.contextOptions){
gl = canvas.getContext(contextName, this.contextOptions);
}else{
gl = canvas.getContext(contextName);
};
} catch (e) {
gl = null;
}
if(!gl || typeof gl.getParameter !== "function") {
gl = null;
}
++nameIndex;
};
this.contextGL = gl;
};
/**
* Initialize GL shader program
*/
YUVCanvas.prototype.initProgram = function() {
var gl = this.contextGL;
// vertex shader is the same for all types
var vertexShaderScript;
var fragmentShaderScript;
if (this.type === "yuv420"){
vertexShaderScript = [
'attribute vec4 vertexPos;',
'attribute vec4 texturePos;',
'attribute vec4 uTexturePos;',
'attribute vec4 vTexturePos;',
'varying vec2 textureCoord;',
'varying vec2 uTextureCoord;',
'varying vec2 vTextureCoord;',
'void main()',
'{',
' gl_Position = vertexPos;',
' textureCoord = texturePos.xy;',
' uTextureCoord = uTexturePos.xy;',
' vTextureCoord = vTexturePos.xy;',
'}'
].join('\n');
fragmentShaderScript = [
'precision highp float;',
'varying highp vec2 textureCoord;',
'varying highp vec2 uTextureCoord;',
'varying highp vec2 vTextureCoord;',
'uniform sampler2D ySampler;',
'uniform sampler2D uSampler;',
'uniform sampler2D vSampler;',
'uniform mat4 YUV2RGB;',
'void main(void) {',
' highp float y = texture2D(ySampler, textureCoord).r;',
' highp float u = texture2D(uSampler, uTextureCoord).r;',
' highp float v = texture2D(vSampler, vTextureCoord).r;',
' gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;',
'}'
].join('\n');
}else if (this.type === "yuv422"){
vertexShaderScript = [
'attribute vec4 vertexPos;',
'attribute vec4 texturePos;',
'varying vec2 textureCoord;',
'void main()',
'{',
' gl_Position = vertexPos;',
' textureCoord = texturePos.xy;',
'}'
].join('\n');
fragmentShaderScript = [
'precision highp float;',
'varying highp vec2 textureCoord;',
'uniform sampler2D sampler;',
'uniform highp vec2 resolution;',
'uniform mat4 YUV2RGB;',
'void main(void) {',
' highp float texPixX = 1.0 / resolution.x;',
' highp float logPixX = 2.0 / resolution.x;', // half the resolution of the texture
' highp float logHalfPixX = 4.0 / resolution.x;', // half of the logical resolution so every 4th pixel
' highp float steps = floor(textureCoord.x / logPixX);',
' highp float uvSteps = floor(textureCoord.x / logHalfPixX);',
' highp float y = texture2D(sampler, vec2((logPixX * steps) + texPixX, textureCoord.y)).r;',
' highp float u = texture2D(sampler, vec2((logHalfPixX * uvSteps), textureCoord.y)).r;',
' highp float v = texture2D(sampler, vec2((logHalfPixX * uvSteps) + texPixX + texPixX, textureCoord.y)).r;',
//' highp float y = texture2D(sampler, textureCoord).r;',
//' gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;',
' gl_FragColor = vec4(y, u, v, 1.0) * YUV2RGB;',
'}'
].join('\n');
};
var YUV2RGB = [];
if (this.conversionType == "rec709") {
// ITU-T Rec. 709
YUV2RGB = [
1.16438, 0.00000, 1.79274, -0.97295,
1.16438, -0.21325, -0.53291, 0.30148,
1.16438, 2.11240, 0.00000, -1.13340,
0, 0, 0, 1,
];
} else {
// assume ITU-T Rec. 601
YUV2RGB = [
1.16438, 0.00000, 1.59603, -0.87079,
1.16438, -0.39176, -0.81297, 0.52959,
1.16438, 2.01723, 0.00000, -1.08139,
0, 0, 0, 1
];
};
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderScript);
gl.compileShader(vertexShader);
if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
console.log('Vertex shader failed to compile: ' + gl.getShaderInfoLog(vertexShader));
}
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderScript);
gl.compileShader(fragmentShader);
if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
console.log('Fragment shader failed to compile: ' + gl.getShaderInfoLog(fragmentShader));
}
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if(!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.log('Program failed to compile: ' + gl.getProgramInfoLog(program));
}
gl.useProgram(program);
var YUV2RGBRef = gl.getUniformLocation(program, 'YUV2RGB');
gl.uniformMatrix4fv(YUV2RGBRef, false, YUV2RGB);
this.shaderProgram = program;
};
/**
* Initialize vertex buffers and attach to shader program
*/
YUVCanvas.prototype.initBuffers = function() {
var gl = this.contextGL;
var program = this.shaderProgram;
var vertexPosBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 1, -1, 1, 1, -1, -1, -1]), gl.STATIC_DRAW);
var vertexPosRef = gl.getAttribLocation(program, 'vertexPos');
gl.enableVertexAttribArray(vertexPosRef);
gl.vertexAttribPointer(vertexPosRef, 2, gl.FLOAT, false, 0, 0);
if (this.animationTime){
var animationTime = this.animationTime;
var timePassed = 0;
var stepTime = 15;
var aniFun = function(){
timePassed += stepTime;
var mul = ( 1 * timePassed ) / animationTime;
if (timePassed >= animationTime){
mul = 1;
}else{
setTimeout(aniFun, stepTime);
};
var neg = -1 * mul;
var pos = 1 * mul;
var vertexPosBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([pos, pos, neg, pos, pos, neg, neg, neg]), gl.STATIC_DRAW);
var vertexPosRef = gl.getAttribLocation(program, 'vertexPos');
gl.enableVertexAttribArray(vertexPosRef);
gl.vertexAttribPointer(vertexPosRef, 2, gl.FLOAT, false, 0, 0);
try{
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}catch(e){};
};
aniFun();
};
var texturePosBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.STATIC_DRAW);
var texturePosRef = gl.getAttribLocation(program, 'texturePos');
gl.enableVertexAttribArray(texturePosRef);
gl.vertexAttribPointer(texturePosRef, 2, gl.FLOAT, false, 0, 0);
this.texturePosBuffer = texturePosBuffer;
if (this.type === "yuv420"){
var uTexturePosBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, uTexturePosBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.STATIC_DRAW);
var uTexturePosRef = gl.getAttribLocation(program, 'uTexturePos');
gl.enableVertexAttribArray(uTexturePosRef);
gl.vertexAttribPointer(uTexturePosRef, 2, gl.FLOAT, false, 0, 0);
this.uTexturePosBuffer = uTexturePosBuffer;
var vTexturePosBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vTexturePosBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.STATIC_DRAW);
var vTexturePosRef = gl.getAttribLocation(program, 'vTexturePos');
gl.enableVertexAttribArray(vTexturePosRef);
gl.vertexAttribPointer(vTexturePosRef, 2, gl.FLOAT, false, 0, 0);
this.vTexturePosBuffer = vTexturePosBuffer;
};
};
/**
* Initialize GL textures and attach to shader program
*/
YUVCanvas.prototype.initTextures = function() {
var gl = this.contextGL;
var program = this.shaderProgram;
if (this.type === "yuv420"){
var yTextureRef = this.initTexture();
var ySamplerRef = gl.getUniformLocation(program, 'ySampler');
gl.uniform1i(ySamplerRef, 0);
this.yTextureRef = yTextureRef;
var uTextureRef = this.initTexture();
var uSamplerRef = gl.getUniformLocation(program, 'uSampler');
gl.uniform1i(uSamplerRef, 1);
this.uTextureRef = uTextureRef;
var vTextureRef = this.initTexture();
var vSamplerRef = gl.getUniformLocation(program, 'vSampler');
gl.uniform1i(vSamplerRef, 2);
this.vTextureRef = vTextureRef;
}else if (this.type === "yuv422"){
// only one texture for 422
var textureRef = this.initTexture();
var samplerRef = gl.getUniformLocation(program, 'sampler');
gl.uniform1i(samplerRef, 0);
this.textureRef = textureRef;
};
};
/**
* Create and configure a single texture
*/
YUVCanvas.prototype.initTexture = function() {
var gl = this.contextGL;
var textureRef = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, textureRef);
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);
gl.bindTexture(gl.TEXTURE_2D, null);
return textureRef;
};
/**
* Draw picture data to the canvas.
* If this object is using WebGL, the data must be an I420 formatted ArrayBuffer,
* Otherwise, data must be an RGBA formatted ArrayBuffer.
*/
YUVCanvas.prototype.drawNextOutputPicture = function(width, height, croppingParams, data) {
var gl = this.contextGL;
if(gl) {
this.drawNextOuptutPictureGL(width, height, croppingParams, data);
} else {
this.drawNextOuptutPictureRGBA(width, height, croppingParams, data);
}
};
/**
* Draw next output picture using ARGB data on a 2d canvas.
*/
YUVCanvas.prototype.drawNextOuptutPictureRGBA = function(width, height, croppingParams, data) {
var canvas = this.canvasElement;
var croppingParams = null;
var argbData = data;
var ctx = canvas.getContext('2d');
var imageData = ctx.getImageData(0, 0, width, height);
imageData.data.set(argbData);
if(croppingParams === null) {
ctx.putImageData(imageData, 0, 0);
} else {
ctx.putImageData(imageData, -croppingParams.left, -croppingParams.top, 0, 0, croppingParams.width, croppingParams.height);
}
};
return YUVCanvas;
}));