YUVCanvas.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. //
  2. // Copyright (c) 2015 Paperspace Co. All rights reserved.
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //
  22. // universal module definition
  23. (function (root, factory) {
  24. if (typeof define === 'function' && define.amd) {
  25. // AMD. Register as an anonymous module.
  26. define([], factory);
  27. } else if (typeof exports === 'object') {
  28. // Node. Does not work with strict CommonJS, but
  29. // only CommonJS-like environments that support module.exports,
  30. // like Node.
  31. module.exports = factory();
  32. } else {
  33. // Browser globals (root is window)
  34. root.YUVCanvas = factory();
  35. }
  36. }(this, function () {
  37. /**
  38. * This class can be used to render output pictures from an H264bsdDecoder to a canvas element.
  39. * If available the content is rendered using WebGL.
  40. */
  41. function YUVCanvas(parOptions) {
  42. parOptions = parOptions || {};
  43. this.canvasElement = parOptions.canvas || document.createElement("canvas");
  44. this.contextOptions = parOptions.contextOptions;
  45. this.type = parOptions.type || "yuv420";
  46. this.customYUV444 = parOptions.customYUV444;
  47. this.conversionType = parOptions.conversionType || "rec601";
  48. this.width = parOptions.width || 640;
  49. this.height = parOptions.height || 320;
  50. this.animationTime = parOptions.animationTime || 0;
  51. this.canvasElement.width = this.width;
  52. this.canvasElement.height = this.height;
  53. this.initContextGL();
  54. if(this.contextGL) {
  55. this.initProgram();
  56. this.initBuffers();
  57. this.initTextures();
  58. };
  59. /**
  60. * Draw the next output picture using WebGL
  61. */
  62. if (this.type === "yuv420"){
  63. this.drawNextOuptutPictureGL = function(par) {
  64. var gl = this.contextGL;
  65. var texturePosBuffer = this.texturePosBuffer;
  66. var uTexturePosBuffer = this.uTexturePosBuffer;
  67. var vTexturePosBuffer = this.vTexturePosBuffer;
  68. var yTextureRef = this.yTextureRef;
  69. var uTextureRef = this.uTextureRef;
  70. var vTextureRef = this.vTextureRef;
  71. var yData = par.yData;
  72. var uData = par.uData;
  73. var vData = par.vData;
  74. var width = this.width;
  75. var height = this.height;
  76. var yDataPerRow = par.yDataPerRow || width;
  77. var yRowCnt = par.yRowCnt || height;
  78. var uDataPerRow = par.uDataPerRow || (width / 2);
  79. var uRowCnt = par.uRowCnt || (height / 2);
  80. var vDataPerRow = par.vDataPerRow || uDataPerRow;
  81. var vRowCnt = par.vRowCnt || uRowCnt;
  82. gl.viewport(0, 0, width, height);
  83. var tTop = 0;
  84. var tLeft = 0;
  85. var tBottom = height / yRowCnt;
  86. var tRight = width / yDataPerRow;
  87. var texturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);
  88. gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
  89. gl.bufferData(gl.ARRAY_BUFFER, texturePosValues, gl.DYNAMIC_DRAW);
  90. if (this.customYUV444){
  91. tBottom = height / uRowCnt;
  92. tRight = width / uDataPerRow;
  93. }else{
  94. tBottom = (height / 2) / uRowCnt;
  95. tRight = (width / 2) / uDataPerRow;
  96. };
  97. var uTexturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);
  98. gl.bindBuffer(gl.ARRAY_BUFFER, uTexturePosBuffer);
  99. gl.bufferData(gl.ARRAY_BUFFER, uTexturePosValues, gl.DYNAMIC_DRAW);
  100. if (this.customYUV444){
  101. tBottom = height / vRowCnt;
  102. tRight = width / vDataPerRow;
  103. }else{
  104. tBottom = (height / 2) / vRowCnt;
  105. tRight = (width / 2) / vDataPerRow;
  106. };
  107. var vTexturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);
  108. gl.bindBuffer(gl.ARRAY_BUFFER, vTexturePosBuffer);
  109. gl.bufferData(gl.ARRAY_BUFFER, vTexturePosValues, gl.DYNAMIC_DRAW);
  110. gl.activeTexture(gl.TEXTURE0);
  111. gl.bindTexture(gl.TEXTURE_2D, yTextureRef);
  112. gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, yDataPerRow, yRowCnt, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, yData);
  113. gl.activeTexture(gl.TEXTURE1);
  114. gl.bindTexture(gl.TEXTURE_2D, uTextureRef);
  115. gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, uDataPerRow, uRowCnt, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, uData);
  116. gl.activeTexture(gl.TEXTURE2);
  117. gl.bindTexture(gl.TEXTURE_2D, vTextureRef);
  118. gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, vDataPerRow, vRowCnt, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, vData);
  119. gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
  120. };
  121. }else if (this.type === "yuv422"){
  122. this.drawNextOuptutPictureGL = function(par) {
  123. var gl = this.contextGL;
  124. var texturePosBuffer = this.texturePosBuffer;
  125. var textureRef = this.textureRef;
  126. var data = par.data;
  127. var width = this.width;
  128. var height = this.height;
  129. var dataPerRow = par.dataPerRow || (width * 2);
  130. var rowCnt = par.rowCnt || height;
  131. gl.viewport(0, 0, width, height);
  132. var tTop = 0;
  133. var tLeft = 0;
  134. var tBottom = height / rowCnt;
  135. var tRight = width / (dataPerRow / 2);
  136. var texturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);
  137. gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
  138. gl.bufferData(gl.ARRAY_BUFFER, texturePosValues, gl.DYNAMIC_DRAW);
  139. gl.uniform2f(gl.getUniformLocation(this.shaderProgram, 'resolution'), dataPerRow, height);
  140. gl.activeTexture(gl.TEXTURE0);
  141. gl.bindTexture(gl.TEXTURE_2D, textureRef);
  142. gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, dataPerRow, rowCnt, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data);
  143. gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
  144. };
  145. };
  146. };
  147. /**
  148. * Returns true if the canvas supports WebGL
  149. */
  150. YUVCanvas.prototype.isWebGL = function() {
  151. return this.contextGL;
  152. };
  153. /**
  154. * Create the GL context from the canvas element
  155. */
  156. YUVCanvas.prototype.initContextGL = function() {
  157. var canvas = this.canvasElement;
  158. var gl = null;
  159. var validContextNames = ["webgl", "experimental-webgl", "moz-webgl", "webkit-3d"];
  160. var nameIndex = 0;
  161. while(!gl && nameIndex < validContextNames.length) {
  162. var contextName = validContextNames[nameIndex];
  163. try {
  164. if (this.contextOptions){
  165. gl = canvas.getContext(contextName, this.contextOptions);
  166. }else{
  167. gl = canvas.getContext(contextName);
  168. };
  169. } catch (e) {
  170. gl = null;
  171. }
  172. if(!gl || typeof gl.getParameter !== "function") {
  173. gl = null;
  174. }
  175. ++nameIndex;
  176. };
  177. this.contextGL = gl;
  178. };
  179. /**
  180. * Initialize GL shader program
  181. */
  182. YUVCanvas.prototype.initProgram = function() {
  183. var gl = this.contextGL;
  184. // vertex shader is the same for all types
  185. var vertexShaderScript;
  186. var fragmentShaderScript;
  187. if (this.type === "yuv420"){
  188. vertexShaderScript = [
  189. 'attribute vec4 vertexPos;',
  190. 'attribute vec4 texturePos;',
  191. 'attribute vec4 uTexturePos;',
  192. 'attribute vec4 vTexturePos;',
  193. 'varying vec2 textureCoord;',
  194. 'varying vec2 uTextureCoord;',
  195. 'varying vec2 vTextureCoord;',
  196. 'void main()',
  197. '{',
  198. ' gl_Position = vertexPos;',
  199. ' textureCoord = texturePos.xy;',
  200. ' uTextureCoord = uTexturePos.xy;',
  201. ' vTextureCoord = vTexturePos.xy;',
  202. '}'
  203. ].join('\n');
  204. fragmentShaderScript = [
  205. 'precision highp float;',
  206. 'varying highp vec2 textureCoord;',
  207. 'varying highp vec2 uTextureCoord;',
  208. 'varying highp vec2 vTextureCoord;',
  209. 'uniform sampler2D ySampler;',
  210. 'uniform sampler2D uSampler;',
  211. 'uniform sampler2D vSampler;',
  212. 'uniform mat4 YUV2RGB;',
  213. 'void main(void) {',
  214. ' highp float y = texture2D(ySampler, textureCoord).r;',
  215. ' highp float u = texture2D(uSampler, uTextureCoord).r;',
  216. ' highp float v = texture2D(vSampler, vTextureCoord).r;',
  217. ' gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;',
  218. '}'
  219. ].join('\n');
  220. }else if (this.type === "yuv422"){
  221. vertexShaderScript = [
  222. 'attribute vec4 vertexPos;',
  223. 'attribute vec4 texturePos;',
  224. 'varying vec2 textureCoord;',
  225. 'void main()',
  226. '{',
  227. ' gl_Position = vertexPos;',
  228. ' textureCoord = texturePos.xy;',
  229. '}'
  230. ].join('\n');
  231. fragmentShaderScript = [
  232. 'precision highp float;',
  233. 'varying highp vec2 textureCoord;',
  234. 'uniform sampler2D sampler;',
  235. 'uniform highp vec2 resolution;',
  236. 'uniform mat4 YUV2RGB;',
  237. 'void main(void) {',
  238. ' highp float texPixX = 1.0 / resolution.x;',
  239. ' highp float logPixX = 2.0 / resolution.x;', // half the resolution of the texture
  240. ' highp float logHalfPixX = 4.0 / resolution.x;', // half of the logical resolution so every 4th pixel
  241. ' highp float steps = floor(textureCoord.x / logPixX);',
  242. ' highp float uvSteps = floor(textureCoord.x / logHalfPixX);',
  243. ' highp float y = texture2D(sampler, vec2((logPixX * steps) + texPixX, textureCoord.y)).r;',
  244. ' highp float u = texture2D(sampler, vec2((logHalfPixX * uvSteps), textureCoord.y)).r;',
  245. ' highp float v = texture2D(sampler, vec2((logHalfPixX * uvSteps) + texPixX + texPixX, textureCoord.y)).r;',
  246. //' highp float y = texture2D(sampler, textureCoord).r;',
  247. //' gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;',
  248. ' gl_FragColor = vec4(y, u, v, 1.0) * YUV2RGB;',
  249. '}'
  250. ].join('\n');
  251. };
  252. var YUV2RGB = [];
  253. if (this.conversionType == "rec709") {
  254. // ITU-T Rec. 709
  255. YUV2RGB = [
  256. 1.16438, 0.00000, 1.79274, -0.97295,
  257. 1.16438, -0.21325, -0.53291, 0.30148,
  258. 1.16438, 2.11240, 0.00000, -1.13340,
  259. 0, 0, 0, 1,
  260. ];
  261. } else {
  262. // assume ITU-T Rec. 601
  263. YUV2RGB = [
  264. 1.16438, 0.00000, 1.59603, -0.87079,
  265. 1.16438, -0.39176, -0.81297, 0.52959,
  266. 1.16438, 2.01723, 0.00000, -1.08139,
  267. 0, 0, 0, 1
  268. ];
  269. };
  270. var vertexShader = gl.createShader(gl.VERTEX_SHADER);
  271. gl.shaderSource(vertexShader, vertexShaderScript);
  272. gl.compileShader(vertexShader);
  273. if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
  274. console.log('Vertex shader failed to compile: ' + gl.getShaderInfoLog(vertexShader));
  275. }
  276. var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
  277. gl.shaderSource(fragmentShader, fragmentShaderScript);
  278. gl.compileShader(fragmentShader);
  279. if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
  280. console.log('Fragment shader failed to compile: ' + gl.getShaderInfoLog(fragmentShader));
  281. }
  282. var program = gl.createProgram();
  283. gl.attachShader(program, vertexShader);
  284. gl.attachShader(program, fragmentShader);
  285. gl.linkProgram(program);
  286. if(!gl.getProgramParameter(program, gl.LINK_STATUS)) {
  287. console.log('Program failed to compile: ' + gl.getProgramInfoLog(program));
  288. }
  289. gl.useProgram(program);
  290. var YUV2RGBRef = gl.getUniformLocation(program, 'YUV2RGB');
  291. gl.uniformMatrix4fv(YUV2RGBRef, false, YUV2RGB);
  292. this.shaderProgram = program;
  293. };
  294. /**
  295. * Initialize vertex buffers and attach to shader program
  296. */
  297. YUVCanvas.prototype.initBuffers = function() {
  298. var gl = this.contextGL;
  299. var program = this.shaderProgram;
  300. var vertexPosBuffer = gl.createBuffer();
  301. gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer);
  302. gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 1, -1, 1, 1, -1, -1, -1]), gl.STATIC_DRAW);
  303. var vertexPosRef = gl.getAttribLocation(program, 'vertexPos');
  304. gl.enableVertexAttribArray(vertexPosRef);
  305. gl.vertexAttribPointer(vertexPosRef, 2, gl.FLOAT, false, 0, 0);
  306. if (this.animationTime){
  307. var animationTime = this.animationTime;
  308. var timePassed = 0;
  309. var stepTime = 15;
  310. var aniFun = function(){
  311. timePassed += stepTime;
  312. var mul = ( 1 * timePassed ) / animationTime;
  313. if (timePassed >= animationTime){
  314. mul = 1;
  315. }else{
  316. setTimeout(aniFun, stepTime);
  317. };
  318. var neg = -1 * mul;
  319. var pos = 1 * mul;
  320. var vertexPosBuffer = gl.createBuffer();
  321. gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer);
  322. gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([pos, pos, neg, pos, pos, neg, neg, neg]), gl.STATIC_DRAW);
  323. var vertexPosRef = gl.getAttribLocation(program, 'vertexPos');
  324. gl.enableVertexAttribArray(vertexPosRef);
  325. gl.vertexAttribPointer(vertexPosRef, 2, gl.FLOAT, false, 0, 0);
  326. try{
  327. gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
  328. }catch(e){};
  329. };
  330. aniFun();
  331. };
  332. var texturePosBuffer = gl.createBuffer();
  333. gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
  334. gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.STATIC_DRAW);
  335. var texturePosRef = gl.getAttribLocation(program, 'texturePos');
  336. gl.enableVertexAttribArray(texturePosRef);
  337. gl.vertexAttribPointer(texturePosRef, 2, gl.FLOAT, false, 0, 0);
  338. this.texturePosBuffer = texturePosBuffer;
  339. if (this.type === "yuv420"){
  340. var uTexturePosBuffer = gl.createBuffer();
  341. gl.bindBuffer(gl.ARRAY_BUFFER, uTexturePosBuffer);
  342. gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.STATIC_DRAW);
  343. var uTexturePosRef = gl.getAttribLocation(program, 'uTexturePos');
  344. gl.enableVertexAttribArray(uTexturePosRef);
  345. gl.vertexAttribPointer(uTexturePosRef, 2, gl.FLOAT, false, 0, 0);
  346. this.uTexturePosBuffer = uTexturePosBuffer;
  347. var vTexturePosBuffer = gl.createBuffer();
  348. gl.bindBuffer(gl.ARRAY_BUFFER, vTexturePosBuffer);
  349. gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.STATIC_DRAW);
  350. var vTexturePosRef = gl.getAttribLocation(program, 'vTexturePos');
  351. gl.enableVertexAttribArray(vTexturePosRef);
  352. gl.vertexAttribPointer(vTexturePosRef, 2, gl.FLOAT, false, 0, 0);
  353. this.vTexturePosBuffer = vTexturePosBuffer;
  354. };
  355. };
  356. /**
  357. * Initialize GL textures and attach to shader program
  358. */
  359. YUVCanvas.prototype.initTextures = function() {
  360. var gl = this.contextGL;
  361. var program = this.shaderProgram;
  362. if (this.type === "yuv420"){
  363. var yTextureRef = this.initTexture();
  364. var ySamplerRef = gl.getUniformLocation(program, 'ySampler');
  365. gl.uniform1i(ySamplerRef, 0);
  366. this.yTextureRef = yTextureRef;
  367. var uTextureRef = this.initTexture();
  368. var uSamplerRef = gl.getUniformLocation(program, 'uSampler');
  369. gl.uniform1i(uSamplerRef, 1);
  370. this.uTextureRef = uTextureRef;
  371. var vTextureRef = this.initTexture();
  372. var vSamplerRef = gl.getUniformLocation(program, 'vSampler');
  373. gl.uniform1i(vSamplerRef, 2);
  374. this.vTextureRef = vTextureRef;
  375. }else if (this.type === "yuv422"){
  376. // only one texture for 422
  377. var textureRef = this.initTexture();
  378. var samplerRef = gl.getUniformLocation(program, 'sampler');
  379. gl.uniform1i(samplerRef, 0);
  380. this.textureRef = textureRef;
  381. };
  382. };
  383. /**
  384. * Create and configure a single texture
  385. */
  386. YUVCanvas.prototype.initTexture = function() {
  387. var gl = this.contextGL;
  388. var textureRef = gl.createTexture();
  389. gl.bindTexture(gl.TEXTURE_2D, textureRef);
  390. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  391. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  392. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  393. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  394. gl.bindTexture(gl.TEXTURE_2D, null);
  395. return textureRef;
  396. };
  397. /**
  398. * Draw picture data to the canvas.
  399. * If this object is using WebGL, the data must be an I420 formatted ArrayBuffer,
  400. * Otherwise, data must be an RGBA formatted ArrayBuffer.
  401. */
  402. YUVCanvas.prototype.drawNextOutputPicture = function(width, height, croppingParams, data) {
  403. var gl = this.contextGL;
  404. if(gl) {
  405. this.drawNextOuptutPictureGL(width, height, croppingParams, data);
  406. } else {
  407. this.drawNextOuptutPictureRGBA(width, height, croppingParams, data);
  408. }
  409. };
  410. /**
  411. * Draw next output picture using ARGB data on a 2d canvas.
  412. */
  413. YUVCanvas.prototype.drawNextOuptutPictureRGBA = function(width, height, croppingParams, data) {
  414. var canvas = this.canvasElement;
  415. var croppingParams = null;
  416. var argbData = data;
  417. var ctx = canvas.getContext('2d');
  418. var imageData = ctx.getImageData(0, 0, width, height);
  419. imageData.data.set(argbData);
  420. if(croppingParams === null) {
  421. ctx.putImageData(imageData, 0, 0);
  422. } else {
  423. ctx.putImageData(imageData, -croppingParams.left, -croppingParams.top, 0, 0, croppingParams.width, croppingParams.height);
  424. }
  425. };
  426. return YUVCanvas;
  427. }));