Skip to content

Instantly share code, notes, and snippets.

@mathdoodle
Last active August 28, 2023 20:31
Show Gist options
  • Save mathdoodle/ff65b6e53f980bdbc14a to your computer and use it in GitHub Desktop.
Save mathdoodle/ff65b6e53f980bdbc14a to your computer and use it in GitHub Desktop.
Warping with WebGL
{
"uuid": "f27b5235-e517-4fc9-8467-1270bbac7bbf",
"description": "Warping with WebGL",
"dependencies": {},
"operatorOverloading": false
}
<!doctype html>
<html>
<head>
<!-- Copyright � Microsoft Corporation. All Rights Reserved. -->
<!-- Demo Author: Frank Olivier, Microsoft Corporation -->
<!-- Updates by Jay Munro -->
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Photo warping with WebGL</title>
<style>
/* STYLE-MARKER */
</style>
<!-- SCRIPTS-MARKER -->
</head>
<body>
<h1 id="DemoTitle">
Photo warping with WebGL
</h1>
<h2>click and drag</h2>
<div id="DemoContent">
<div style='width:600px; height:600px'>
<div style='position: relative'>
<!-- WebGL canvas -->
<canvas style='position: absolute' id='webglcanvas' width='600' height='600'></canvas>
<!-- notation cavas -->
<canvas style='position: absolute' id='2dcanvas' width='600' height='600'></canvas>
</div>
</div>
<div style="text-align:center">
<button id="openphoto1" onclick="OpenPhoto1()">Open a photo</button>
<span id="openphoto2" style="display:none">Pick a photo: <input type="file" id="files" /></span> <br />
<button onclick="undo()">Undo</button>
<button onclick="reset()">Start over</button>
<button onclick="save()">Save</button>
</div>
</div>
<div id="ErrorMessage" style="display:none">
<div class="heading">Sorry!</div>
This demonstation requires a browser with WebGL support.
</div>
<div id="log"></div>
<div id="Details">
<div class="heading">WebGL 101</div>
To render the warped photo above, a mesh of 400 triangle coordinates, a photo, a vertex &
fragment shader program and uniform points are uploaded to the GPU using WebGL.<br />
<br />
<!-- The show uniform points checkbox -->
<label><input type="checkbox" name="showUniforms" id="showUniforms" onchange="renderer.changeMode();renderer.render()" />Show uniform points</label>
<div>
<label><input type="radio" name="rendertype" id="renderLines" onclick="renderer.render()" />Show triangle mesh</label>
<label><input type="radio" name="rendertype" id="renderTriangles" onclick="renderer.render()" checked />Show rendered photo</label>
</div>
<br />
When you click and drag on the photo, new uniform points are set on the GPU...<br /><br />
<div class="heading">Vertex shader</div>
<img src="vertex.png"/><br />
...The GPU runs the vertex shader below to distort the mesh using the uniform points...<br /><br />
<pre id="vertexshadersource"></pre>
<br />
<div class="heading">Fragment shader</div>
<img src="fragment.png"/><br />
...and the fragment shader paints photo pixels using the distorted mesh.<br /><br />
<pre id="fragmentshadersource"></pre>
<br />
<!-- For more information on WebGL, see <a href="http://docs.webplatform.org/wiki/webgl">webplatform.org</a>.</div> -->
</div>
<script>
// CODE-MARKER
</script>
<script id="2d-vertex-shader" type="x-shader/x-vertex">
// outgoing coordinate
varying vec2 v_texCoord;
// incoming coordinate (point)
attribute vec2 a_texCoord;
// maximum number of changes to grid
#define MAXPOINTS 10
uniform vec2 p1[MAXPOINTS]; // Where the drag started
uniform vec2 p2[MAXPOINTS]; // Where the drag ended
void main() {
v_texCoord = a_texCoord;
// Set up position variable with current coordinate normalized from 0 - 1 to -1 to 1 range
vec2 position = a_texCoord * 2.0 - 1.0;
for (int i = 0; i < MAXPOINTS; i++) // loop through
{
float dragdistance = distance(p1[i], p2[i]); // Calculate the distance between two start and end of mouse drag for each of the drags
float mydistance = distance(p1[i], position); // Calculate the distance between the start of the mouse drag and the last position
if (mydistance < dragdistance)
{
vec2 maxdistort = (p2[i] - p1[i]) / 4.0; // only affect vertices within 4 x the drag distance (
float normalizeddistance = mydistance / dragdistance;
float normalizedimpact = (cos(normalizeddistance*3.14159265359)+1.0)/2.0;
position += (maxdistort * normalizedimpact);
}
}
// gl_Position always specifies where to render this vector
gl_Position = vec4(position, 0.0, 1.0); // x,y,z,
}
</script>
<!-- fragment shader -->
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
// uniform to use for texture
uniform sampler2D u_image;
// Output of the vertex shader
varying vec2 v_texCoord;
void main() {
// Specify the color to make the current pixel.
gl_FragColor = texture2D(u_image, v_texCoord);
}
</script>
<!-- fragment shader -->
<script id="red" type="x-shader/x-fragment">
precision mediump float;
varying vec2 v_texCoord;
// Set a solid color for the grid
void main() {
gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
}
</script>
</body>
</html>
'use strict';
window.onload = main; // Startup
/**
* Point - converts incoming values to a -1 to 1 range.
*/
class Point {
public x: number;
public y: number;
constructor(x: number, y: number) {
if (x<-1) x = -1;
if (y<-1) y = -1;
if (x>1) x = 1;
if (y>1) y = 1;
this.x = x;
this.y = y;
}
}
/**
* A new mouse manipulation.
*/
class Move {
public point1: Point;
public point2: Point;
constructor(point: Point) {
this.point1 = new Point(point.x, point.y);
this.point2 = new Point(point.x, point.y);
}
move(point: Point) {
this.point2.x = point.x;
this.point2.y = point.y;
}
}
var renderer = new function () {
var gl: WebGLRenderingContext; // Handle to the context.
var lineprogram: WebGLProgram; // Handle to GLSL program that draws lines.
var pictureprogram: WebGLProgram; // Handle to GLSL program that draws a picture.
this.texCoordLocation; // Location of the texture for the picture fragment shader.
this.texCoordLocation2; // Location of the texture for the line fragment shader.
this.texCoordBuffer; // The buffer for the texture for the picture fragment shader.
this.texCoordBuffer2; // The buffer for the texture for the line fragment shader.
var moves = new Array<Move>();
var MAXMOVES = 10;
var currentMove = 0;
var resolution = 20; // Resolution of the mesh.
// First init called by main().
// Initialize the gl context variable.
this.init = function () {
// Get a context from our canvas object with id = "webglcanvas".
var canvas = <HTMLCanvasElement>document.getElementById("webglcanvas");
try {
// Get the context into a local gl and and a public gl.
// Use preserveDrawingBuffer:true to keep the drawing buffer after presentation
var gl: WebGLRenderingContext = this.gl = canvas.getContext("experimental-webgl", { preserveDrawingBuffer: true });
}
catch (e) {
// Fail quietly
}
// If we can't get a WebGL context (no WebGL support), then display an error message.
if (!this.gl) {
document.getElementById("ErrorMessage").style.display = "block";
return;
}
try {
// Load the GLSL source written in the HTML file.
// Create a program with the two shaders
this.lineprogram = loadProgram(gl, getShader(gl, "2d-vertex-shader"), getShader(gl, "red"));
// Tell webGL to use this program
gl.useProgram(this.lineprogram);
// Look up where the vertex data needs to go.
this.texCoordLocation2 = gl.getAttribLocation(this.lineprogram, "a_texCoord");
// Provide texture coordinates for the rectangle.
this.texCoordBuffer2 = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer2);
// Create a buffer and set it use the array set up above.
// Set it to be modified once, use many.
// createRedGrid sets up the vector array itself.
gl.bufferData(gl.ARRAY_BUFFER, createRedGrid(), gl.STATIC_DRAW); // Fill buffer data
// Turns on the vertex attributes in the GPU program.
gl.enableVertexAttribArray(this.texCoordLocation2);
// Set up the data format for the vertex array - set to points (x/y).
// Use floats.
gl.vertexAttribPointer(this.texCoordLocation2, 2, gl.FLOAT, false, 0, 0);
}
catch (e) {
// Display the fail on the screen if the shaders/program fail.
log('shader fail');
return;
}
try {
var vertexshader = getShader(gl, "2d-vertex-shader");
var fragmentshader = getShader(gl, "2d-fragment-shader");
this.pictureprogram = loadProgram(gl, vertexshader, fragmentshader);
gl.useProgram(this.pictureprogram);
// Put the shader source into the <divs>.
document.getElementById("vertexshadersource").innerText = gl.getShaderSource(vertexshader);
document.getElementById("fragmentshadersource").innerText = gl.getShaderSource(fragmentshader);
// Look up where the vertex data needs to go.
this.texCoordLocation = gl.getAttribLocation(this.pictureprogram, "a_texCoord");
// Provide texture coordinates for the rectangle.
this.texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer);
// createImageGrid sets up the vector array itself
gl.bufferData(gl.ARRAY_BUFFER, createImageGrid(), gl.STATIC_DRAW); // Fill buffer data
gl.vertexAttribPointer(this.texCoordLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(this.texCoordLocation);
// Set up uniforms variables (image).
this.pictureprogram.u_image = gl.getUniformLocation(this.pictureprogram, "u_image");
// Set the texture to use.
gl.uniform1i(this.pictureprogram.u_image, 0);
}
catch (e) {
log('shader fail');
return;
}
this.loadImage();
}
// Load a default image.
this.loadImage = function () {
var image = new Image();
image.onload = function () {
renderer.loadImage2(image);
}
image.src = "photos/demophoto.jpg"; // Default image
}
// load a the user's image.
this.loadImageX = function (dataURL) {
var image = new Image();
image.onload = function () {
renderer.loadImage2(image);
}
image.src = dataURL;
}
// This function does the heavy lifting of creating the texture from the image.
this.loadImage2 = function (image) {
// Convert the image to a square image via the temporary 2d canvas.
var canvas = <HTMLCanvasElement>document.getElementById("2dcanvas");
var ctx = canvas.getContext("2d");
var canvHeight = (<HTMLCanvasElement>document.getElementById("2dcanvas")).height;
var x = 0;
var y = 0;
var xx = canvHeight;
var yy = canvHeight;
ctx.clearRect(0, 0, canvHeight, canvHeight);
// If image isn't square, adjust width, height, and origin so it's centered.
if (image.width < image.height) {
// Change origin and dimensions if the image isn't square.
// Change x, xx
xx = image.width / image.height * canvHeight;
x = (canvHeight - xx) / 2;
}
if (image.width > image.height) {
// Change y, yy
yy = image.height / image.width * canvHeight;
y = (canvHeight - yy) / 2;
}
// Put the image on the canvas, scaled using xx & yy.
ctx.drawImage(image, 0, 0, image.width, image.height, x, y, xx, yy);
var gl: WebGLRenderingContext = this.gl;
// Create a texture object that will contain the image.
var texture = gl.createTexture();
// Bind the texture the target (TEXTURE_2D) of the active texture unit.
gl.bindTexture(gl.TEXTURE_2D, texture);
// Flip the image's Y axis to match the WebGL texture coordinate space.
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, <any>true);
// Set the parameters so we can render any size 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_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
// Upload the resized canvas image into the texture.
// Note: a canvas is used here but can be replaced by an image object.
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
ctx.clearRect(0, 0, canvHeight, canvHeight);
this.reset();
}
// This code checks the Show uniform points checkbox and changes the mode and initializes the canvas.
this.modeOff = 0;
this.modeHint = 1;
this.modeHint2 = 2;
this.modeUniform = 3;
// Modes tell the app to show uniforms or not
this.canvasMode = this.modeHint;
this.changeMode = function () {
if ((<HTMLInputElement>document.getElementById("showUniforms")).checked) {
this.canvasMode = this.modeUniform;
}
else {
this.canvasMode = this.modeOff;
var canvas = <HTMLCanvasElement>document.getElementById("2dcanvas");
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
}
this.render = function () {
var gl: WebGLRenderingContext = this.gl;
// Create two arrays to hold start and end point uniforms
var p1 = new Float32Array(MAXMOVES * 2); //x and y
var p2 = new Float32Array(MAXMOVES * 2); //x and y
// Set up the arrays of points
{
var index = 0;
for (var i = 0; i < MAXMOVES; i++) {
// Working values
var x1: number, y1:number, x2: number, y2: number;
if (moves[i]) {
x1 = moves[i].point1.x;
y1 = moves[i].point1.y;
x2 = moves[i].point2.x;
y2 = moves[i].point2.y;
}
else {
x1 = 1;
y1 = 1;
x2 = 0.9999999;
y2 = 0.9999999;
}
p1[index] = x1;
p1[index + 1] = y1;
p2[index] = x2;
p2[index + 1] = y2;
index += 2;
}
}
// Clear color buffer and set it to light gray
gl.clearColor(1.0, 1.0, 1.0, 0.5);
gl.clear(this.gl.COLOR_BUFFER_BIT);
// This draws either the grid or the photo for stretching
if ((<HTMLInputElement>document.getElementById("renderLines")).checked)
{
gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer2);
gl.useProgram(this.lineprogram);
gl.uniform2fv(gl.getUniformLocation(this.lineprogram, "p1"), p1);
gl.uniform2fv(gl.getUniformLocation(this.lineprogram, "p2"), p2);
gl.vertexAttribPointer(this.texCoordLocation2, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(this.texCoordLocation2);
gl.drawArrays(gl.LINES, 0, resolution * resolution * 10);
}
else
{
gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer);
gl.useProgram(this.pictureprogram);
gl.uniform2fv(gl.getUniformLocation(this.pictureprogram, "p1"), p1);
gl.uniform2fv(gl.getUniformLocation(this.pictureprogram, "p2"), p2);
gl.vertexAttribPointer(this.texCoordLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(this.texCoordLocation);
gl.drawArrays(gl.TRIANGLES, 0, resolution * resolution * 2 * 3);
}
// Draw uniform points
if (this.canvasMode == this.modeUniform) {
var canvas = <HTMLCanvasElement>document.getElementById("2dcanvas");
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < MAXMOVES; i++) {
if (moves[i]) {
var x1 = (moves[i].point1.x + 1) * canvas.width / 2;
var y1 = (-moves[i].point1.y + 1) * canvas.height / 2;
var x2 = (moves[i].point2.x + 1) * canvas.width / 2;
var y2 = (-moves[i].point2.y + 1) * canvas.height / 2;
// The raio is used here to show where the pixel started and ended
var ratio = 0.3;
x2 = x1 + (x2 - x1) * ratio;
y2 = y1 + (y2 - y1) * ratio;
var radius = 6;
ctx.beginPath(); // Start a fresh path
// Create a 2D gradient
var grd = ctx.createLinearGradient(x1, y1, x2, y2);
grd.addColorStop(0, 'pink'); // Set one side to pink
grd.addColorStop(1, 'red'); // The other side to red
ctx.setLineDash([5, 5]); // Use a dotted line
ctx.lineWidth = radius / 2;
ctx.moveTo(x1, y1); // Create a line from start to end poing
ctx.lineTo(x2, y2);
ctx.strokeStyle = grd;
ctx.stroke();
ctx.beginPath(); // Start a new path for pink dot
ctx.arc(x1, y1, radius, 0, 2 * Math.PI, false); // full circle (2*pi)
ctx.fillStyle = 'pink';
ctx.fill();
ctx.beginPath(); // Start a new path for red dot
ctx.arc(x2, y2, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'red';
ctx.fill();
}
}
}
}
// (point) is where the mouse was clicked
this.newMove = function(point) // Where the warp starts (-1 to 1 range)
{
var move = new Move(point);
// Adds move to beginning of moves array (pushes onto array)
moves.unshift(move);
return move;
}
this.reset = function () {
moves = [];
this.render();
}
this.undo = function () {
// Removes the first element in moves array (pops off array)
moves.shift();
this.render();
}
this.save = function () {
// First create a dataURL string from the canvas in jpeg format.
var dataURL = (<HTMLCanvasElement>document.getElementById("webglcanvas")).toDataURL("image/png");
// Split the dataURL and decode it from ASCII to base-64 binary.
var binArray = atob(dataURL.split(',')[1]);
// Create an 8-bit unsigned array
var array = [];
// Add the unicode numeric value of each element to the new array.
for (var i = 0; i < binArray.length; i++) {
array.push(binArray.charCodeAt(i));
}
var blobObject = new Blob([new Uint8Array(array)], { type: 'image/png' });
if (window.navigator.msSaveBlob) {
window.navigator.msSaveBlob(blobObject, 'warpedphoto.png');
}else if (window.navigator['saveBlob']) {
window.navigator['saveBlob'](blobObject, 'warpedphoto.png');
}
else {
dataURL = dataURL.replace("image/png", "image/octet-stream");
window.location.href = dataURL;
// alert("Sorry, your browser does not support navigator.saveBlob");
}
}
// Grid making section
function createRedGrid() {
// Make a 0,0 to 1,1 triangle mesh, using n = resolution steps.
var q = 0.001; // A fudge factor to ensure that the wireframe lines are rendered inside the canvas boundary.
var r = (1 - q * 2) / resolution;
//2 numbers per coord; three coords per triangle; 2 triagles per square; resolution * resolution squares.
var c = new Float32Array(resolution * resolution * 20);
// Array index.
var i = 0;
// Build the mesh top to bottom, left to right.
for (var xs = 0; xs < resolution; xs++) {
for (var ys = 0; ys < resolution; ys++) {
var x = r * xs + q;
var y = r * ys + q;
// Top of square - first triangle.
c[i++] = x;
c[i++] = y;
c[i++] = x + r;
c[i++] = y;
// Center line - hypotonose of triangles.
c[i++] = x;
c[i++] = y + r;
c[i++] = x + r;
c[i++] = y;
// Bottom line of 2nd triangle.
c[i++] = x;
c[i++] = y + r;
c[i++] = x + r;
c[i++] = y + r;
// First triangle, left side.
c[i++] = x;
c[i++] = y;
c[i++] = x;
c[i++] = y + r;
// Right side of 2nd triangle.
c[i++] = x + r;
c[i++] = y;
c[i++] = x + r;
c[i++] = y + r;
}
}
return c;
}
function createImageGrid() {
var q = 0.001;
var r = (1 - q * 2) / resolution;
var c = new Float32Array(resolution * resolution * 12); //2 numbers per coord; three coords per triangle; 2 triagles per square; resolution * resolution squares.
var i = 0;
for (var xs = 0; xs < resolution; xs++) {
for (var ys = 0; ys < resolution; ys++) {
var x = r * xs + q;
var y = r * ys + q;
c[i++] = x;
c[i++] = y;
c[i++] = x + r;
c[i++] = y;
c[i++] = x;
c[i++] = y + r;
c[i++] = x + r;
c[i++] = y;
c[i++] = x;
c[i++] = y + r;
c[i++] = x + r;
c[i++] = y + r;
}
}
return c;
}
}
// getMousePoint
// input - mouse event e
function getMousePoint(e: MouseEvent) {
var x;
var y;
// The standard way to get mouse coordinates
if (e.offsetX) {
x = e.offsetX;
y = e.offsetY;
}
// LayerX and layerY are provided for cross-browser compatibility
else if (e.layerX) {
x = e.layerX;
y = e.layerY;
}
else {
return undefined; //Work around Chrome
}
return normalizedPoint(x, y); // Converts pixels to -1 to 1
}
var inputHandler = new function() {
var move; // Pointer to a uniform variable in the renderer object
this.init = function () {
var canvas = document.getElementById("2dcanvas");
// Set up mouse events on the canvas object.
canvas.onmousedown = function (e) {
console.log("onmousedown");
this.move = renderer.newMove(getMousePoint(e));
}
canvas.onmouseup = function (e) {
console.log("onmouseup");
this.move = undefined;
renderer.render();
}
canvas.onmouseout = function (e) {
console.log("onmouseout");
this.move = undefined;
renderer.render();
};
canvas.onmousemove = function (e) {
console.log("onmousemove");
var point = getMousePoint(e);
if (typeof this.move != 'undefined')
{
if (typeof point != 'undefined')
{
this.move.move(point);
}
renderer.render();
}
};
canvas.ondragstart = function (e) { //Workaround for Chrome
console.log("ondragstart");
e.preventDefault();
};
}
}
/**
* Program starts here.
*/
function main() {
renderer.init(); // Initialize WebGL shapes and image
inputHandler.init(); // Initialize mouse and UI handler
}
function undo() {
renderer.undo();
}
function reset() {
renderer.reset();
}
function save() {
renderer.save();
}
function normalizedPoint(x: number, y: number): Point
{
// converts screen coordinates to -1 to 1
var canvas = <HTMLCanvasElement>document.getElementById("2dcanvas");
x = (x / canvas.width) * 2 - 1;
y = (1 - (y / canvas.height)) * 2 - 1;
return new Point(x, y);
}
// Adds a string to the log in the web page
function log(result: string) {
var resultDiv = document.getElementById("log");
resultDiv.innerHTML += result + "<br />";
}
// Adds a string to the log in the web page; overwrites everything in the log with the new string
function logi(result: string) {
var resultDiv = document.getElementById('log');
resultDiv.innerHTML = result;
}
// Loads a shader from a script tag
// Parameters:
// WebGL context
// id of script element containing the shader to load
function getShader(gl: WebGLRenderingContext, id: string): WebGLShader {
var shaderScript = <HTMLScriptElement>document.getElementById(id);
// error - element with supplied id couldn't be retrieved
if (!shaderScript) {
return null;
}
// If successful, build a string representing the shader source
var str = "";
var k = shaderScript.firstChild;
while (k) {
if (k.nodeType == 3) {
str += k.textContent;
}
k = k.nextSibling;
}
var shader: WebGLShader;
// Create shaders based on the type we set
// note: these types are commonly used, but not required
if (shaderScript.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderScript.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
return null;
}
gl.shaderSource(shader, str);
gl.compileShader(shader);
// Check the compile status, return an error if failed
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.log(gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
function loadProgram(gl: WebGLRenderingContext, vertexShader: WebGLShader, fragmentShader: WebGLShader): WebGLProgram
{
// create a progam object
var program = gl.createProgram();
// attach the two shaders
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
// link everything
gl.linkProgram(program);
// Check the link status
var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!linked) {
// An error occurred while linking
var lastError = gl.getProgramInfoLog(program);
console.warn("Error in program linking:" + lastError);
gl.deleteProgram(program);
return null;
}
// if all is well, return the program object
return program;
};
function handleFileSelect(evt) {
document.getElementById("openphoto1").style.display = "inline";
document.getElementById("openphoto2").style.display = "none";
var files = evt.target.files; // FileList object
// files is a FileList of File objects. List some properties.
var file = files[0];
var reader = new FileReader();
// Closure to capture the file information.
reader.onload = function (e) {
renderer.loadImageX(this.result);
};
// Read in the image file as a data URL.
reader.readAsDataURL(file);
}
document.getElementById('files').addEventListener('change', handleFileSelect, false);
function OpenPhoto1() {
document.getElementById("openphoto1").style.display = "none";
document.getElementById("openphoto2").style.display = "inline";
}
/* Prevent text selection from being displayed when the user drags out of the canvas */
*::selection {
background:transparent;
}
#DemoContent {
margin-top:10px;
}
#DemoContent {
width:600px;
margin-left:auto;
margin-right:auto;
margin-top:10px;
}
#Details {
margin-top:10px;
margin-bottom:10px;
padding:20px;
border-radius:4px;
}
body {
-ms-user-select: none; /* turn off user selection */
font-size: 12pt;
}
canvas {
-ms-touch-action: none; /* turn off panning and zooming */
}
.heading
{
font-size: 20pt;
font-weight: normal;
}
#ErrorMessage {
position:absolute;
top:100px;
left:20%;
right:20%;
background-color:lightcoral;
padding:20px;
border-radius:4px;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment