UNPKG

fisheyegl-video

Version:

A JavaScript library for correcting fisheye, or barrel distortion, in videos in the browser with WebGL

199 lines (156 loc) 6.48 kB
const shaders = require('./shaders'); function compileShader(gl, vertexSrc, fragmentSrc) { function checkCompile(shader) { if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { throw new Error(gl.getShaderInfoLog(shader)); } } const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertexSrc); gl.compileShader(vertexShader); checkCompile(vertexShader); const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragmentSrc); gl.compileShader(fragmentShader); checkCompile(fragmentShader); const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); return program; } function createTexture(glContext) { const texture = glContext.createTexture(); glContext.bindTexture(glContext.TEXTURE_2D, texture); // Because video has to be download over the internet // they might take a moment until it's ready so // put a single pixel in the texture so we can // use it immediately. const level = 0; const internalFormat = glContext.RGBA; const width = 1; const height = 1; const border = 0; const srcFormat = glContext.RGBA; const srcType = glContext.UNSIGNED_BYTE; const pixel = new Uint8Array([0, 0, 255, 255]); // opaque blue glContext.texImage2D(glContext.TEXTURE_2D, level, internalFormat, width, height, border, srcFormat, srcType, pixel); // Turn off mips and set wrapping to clamp to edge so it // will work regardless of the dimensions of the video. glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_WRAP_S, glContext.CLAMP_TO_EDGE); glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_WRAP_T, glContext.CLAMP_TO_EDGE); glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, glContext.LINEAR); glContext.bindTexture(glContext.TEXTURE_2D, texture); return texture; } const FisheyeGl = function FisheyeGl(opts) { const options = opts || {}; const model = { vertex: [ -1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 1.0, 1.0, 0.0, -1.0, 1.0, 0.0, ], indices: [ 0, 1, 2, 0, 2, 3, 2, 1, 0, 3, 2, 0, ], textureCoords: [ 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, ], }; const dist = options.dist || { scale: 0, zoom: 1, zoomAnchor: { x: 0.5, y: 0.5 }, shift: { x: 0.0, y: 0.0 }, k3: 0, k5: 0, k7: 0, }; const { canvasBuffer } = options; const { canvasDestination } = options; const destinationContext = canvasDestination.getContext('2d'); const glContext = canvasBuffer.getContext('webgl'); glContext.viewport(0, 0, glContext.canvas.width, glContext.canvas.height); const program = compileShader(glContext, shaders.vertex, shaders.fragment4); glContext.useProgram(program); const aVertexPosition = glContext.getAttribLocation(program, 'aVertexPosition'); const aTextureCoord = glContext.getAttribLocation(program, 'aTextureCoord'); const uSampler = glContext.getUniformLocation(program, 'uSampler'); const uScale = glContext.getUniformLocation(program, 'uScale'); const uZoom = glContext.getUniformLocation(program, 'uZoom'); const uZoomAnchor = glContext.getUniformLocation(program, 'uZoomAnchor'); const uShift = glContext.getUniformLocation(program, 'uShift'); const uSize = glContext.getUniformLocation(program, 'uSize'); const uDistortion = glContext.getUniformLocation(program, 'uDistortion'); let vertexBuffer; let indexBuffer; let textureBuffer; function createBuffers() { vertexBuffer = glContext.createBuffer(); glContext.bindBuffer(glContext.ARRAY_BUFFER, vertexBuffer); glContext.bufferData(glContext.ARRAY_BUFFER, new Float32Array(model.vertex), glContext.STATIC_DRAW); glContext.bindBuffer(glContext.ARRAY_BUFFER, null); indexBuffer = glContext.createBuffer(); glContext.bindBuffer(glContext.ELEMENT_ARRAY_BUFFER, indexBuffer); glContext.bufferData(glContext.ELEMENT_ARRAY_BUFFER, new Uint16Array(model.indices), glContext.STATIC_DRAW); glContext.bindBuffer(glContext.ELEMENT_ARRAY_BUFFER, null); textureBuffer = glContext.createBuffer(); glContext.bindBuffer(glContext.ARRAY_BUFFER, textureBuffer); glContext.bufferData(glContext.ARRAY_BUFFER, new Float32Array(model.textureCoords), glContext.STATIC_DRAW); glContext.bindBuffer(glContext.ARRAY_BUFFER, null); } createBuffers(); const texture = createTexture(glContext); function applyDistortion() { glContext.clearColor(0.0, 0.0, 0.0, 1.0); glContext.enable(glContext.DEPTH_TEST); glContext.clear(glContext.COLOR_BUFFER_BIT | glContext.DEPTH_BUFFER_BIT); glContext.enableVertexAttribArray(aVertexPosition); glContext.bindBuffer(glContext.ARRAY_BUFFER, vertexBuffer); glContext.vertexAttribPointer(aVertexPosition, 3, glContext.FLOAT, false, 0, 0); glContext.enableVertexAttribArray(aTextureCoord); glContext.bindBuffer(glContext.ARRAY_BUFFER, textureBuffer); glContext.vertexAttribPointer(aTextureCoord, 2, glContext.FLOAT, false, 0, 0); glContext.activeTexture(glContext.TEXTURE0); glContext.bindTexture(glContext.TEXTURE_2D, texture); glContext.uniform1i(uSampler, 0); glContext.uniform1f(uScale, dist.scale); glContext.uniform1f(uZoom, dist.zoom); glContext.uniform2fv(uZoomAnchor, [dist.zoomAnchor.x, dist.zoomAnchor.y]); glContext.uniform2fv(uShift, [dist.shift.x, dist.shift.y]); glContext.uniform2fv(uSize, [glContext.drawingBufferWidth, glContext.drawingBufferHeight]); glContext.uniform3fv(uDistortion, [dist.k3, dist.k5, dist.k7]); glContext.bindBuffer(glContext.ELEMENT_ARRAY_BUFFER, indexBuffer); glContext.drawElements(glContext.TRIANGLES, model.indices.length, glContext.UNSIGNED_SHORT, 0); destinationContext.drawImage(canvasBuffer, 0, 0); } function updateVideoFrame(video) { const level = 0; const internalFormat = glContext.RGBA; const srcFormat = glContext.RGBA; const srcType = glContext.UNSIGNED_BYTE; glContext.texImage2D(glContext.TEXTURE_2D, level, internalFormat, srcFormat, srcType, video); applyDistortion(); } const distorter = { options, dist, applyDistortion, updateVideoFrame, }; return distorter; }; if (typeof (document) !== 'undefined') { window.FisheyeGl = FisheyeGl; } module.exports = FisheyeGl;