(function () { const canvas = document.querySelector(".myCanvas"); const image = document.querySelector(".myPreview__image"); const gl = canvas.getContext("webgl", { preserveDrawingBuffer: true }); const plane = createPlane(gl); const texture = createTexture(gl); const shader = createImageShader(gl); let matrix = []; function update() { gl.clearColor(255.0, 255.0, 255.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); shader.use().setProjection(matrix).setTexture(texture); plane.draw(shader); } function loadTexture() { drawToTexure(gl, texture, image); update(); } function toMatrix(t) { // prettier-ignore return [ t[0], t[1], 0, t[2], t[3], t[4], 0, t[5], 0, 0, 1, 0, t[6], t[7], 0, t[8] ]; } if (image.naturalWidth && image.naturalHeight) { loadTexture(); } else { image.addEventListener("load", () => { loadTexture(); }); } /** * Event handlers */ document.addEventListener("myPointsChanged", (event) => { const points = event.detail.reduce( (result, { x, y }) => [...result, x / 150 - 1, 1 - y / 150], [] ); const transform = new PerspT([1, -1, -1, -1, -1, 1, 1, 1], points); matrix = toMatrix(transform.coeffs); update(); }); /** * Primitives */ function createPlane(gl) { const positionBuffer = createBuffer( gl, [1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0] ); return { draw(shader) { shader.bindPositionBuffer(positionBuffer); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); }, }; } function createImageShader(gl) { const vertexSource = ` attribute vec4 aPosition; uniform mat4 uProjection; varying highp vec2 vTextureCoord; void main() { vTextureCoord = vec2((aPosition.x - 1.0) * 0.5, (aPosition.y - 1.0) * 0.5); gl_Position = vec4(aPosition.x, aPosition.y, 0.0, 1.0) * uProjection; } `; const fragmentSource = ` varying highp vec2 vTextureCoord; uniform sampler2D uSampler; void main() { gl_FragColor = texture2D(uSampler, vTextureCoord); } `; const program = createProgram( gl, createShader(gl, vertexSource, gl.VERTEX_SHADER), createShader(gl, fragmentSource, gl.FRAGMENT_SHADER) ); const positionAttribute = gl.getAttribLocation(program, "aPosition"); const projectionLocation = gl.getUniformLocation(program, "uProjection"); const samplerLocation = gl.getUniformLocation(program, "uSampler"); const instance = { use() { gl.useProgram(program); gl.enableVertexAttribArray(positionAttribute); return instance; }, bindPositionBuffer(buffer) { gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.vertexAttribPointer(positionAttribute, 2, gl.FLOAT, false, 0, 0); }, setProjection(value) { gl.uniformMatrix4fv(projectionLocation, false, value); return instance; }, setTexture(value) { gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, value); gl.uniform1i(samplerLocation, 0); return instance; }, }; return instance; } /** * WebGL helpers * https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial */ function createBuffer(gl, positions) { const positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); return positionBuffer; } function createProgram(gl, vertexShader, fragmentShader) { const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { const info = gl.getProgramInfoLog(program); throw `Could not compile WebGL program. \n\n${info}`; } return program; } function createShader(gl, source, type) { const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { const info = gl.getShaderInfoLog(shader); throw `Could not compile WebGL program. \n\n${info}`; } return shader; } function createTexture(gl) { const texture = gl.createTexture(); const pixel = new Uint8Array([0, 0, 255, 255]); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixel ); return texture; } function drawToTexure(gl, texture, source) { gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source); if (isPowerOf2(source.naturalWidth) && isPowerOf2(source.naturalHeight)) { gl.generateMipmap(gl.TEXTURE_2D); } else { 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_MIN_FILTER, gl.LINEAR); } } function isPowerOf2(value) { return (value & (value - 1)) === 0; } })();