function CanvasWrapper(canvas, properties) {
    this.canvas = canvas;
    this.properties = properties;
    this.sizeX = canvas.width;
    this.sizeY = canvas.height;
    this.updateInternalTextureMeasures();
    this.ratio = canvas.height / canvas.width;
    this.it = 0;
    this.textures = [];
    this.framebuffers = [];
    this.generation = -1;


    this.bindTexture();

    canvas['drawTo'] = this.drawTo.bind(this);
    canvas['readPixels'] = this.readPixels.bind(this);
    canvas['cdyUpdate'] = this.copyTextureToCanvas.bind(this);


    let rawData = createPixelArray(this.sizeXP * this.sizeYP * 4);

    for (let j = 0; j < 2; j++) {
        this.textures[j] = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, this.textures[j]);
        gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.sizeXP, this.sizeYP, 0, gl.RGBA, getPixelType(), rawData);
        if (properties.mipmap)
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, properties.interpolate ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_LINEAR); //always interpolate between 2 mipmap levels NEAREST_MIPMAP_LINEAR
        else
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, properties.interpolate ? gl.LINEAR : gl.NEAREST);

        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, properties.interpolate ? gl.LINEAR : gl.NEAREST);

        if (properties.clamptoedge) {
            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);
        }

        this.framebuffers[j] = gl.createFramebuffer();
        gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffers[j]);
        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.textures[j], 0);
    }

    this.shaderProgram = new ShaderProgram(gl, cgl_resources["copytexture_v"], cgl_resources["copytexture_f"]);
    var posBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);

    var vertices = new Float32Array([-1, -1, 0, 1, -1, 0, -1, 1, 0, 1, 1, 0]);

    var aPosLoc = gl.getAttribLocation(this.shaderProgram.handle, "aPos");
    gl.enableVertexAttribArray(aPosLoc);

    var aTexLoc = gl.getAttribLocation(this.shaderProgram.handle, "aTexCoord");
    gl.enableVertexAttribArray(aTexLoc);

    var texCoords = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);

    var texCoordOffset = vertices.byteLength;

    gl.bufferData(gl.ARRAY_BUFFER, texCoordOffset + texCoords.byteLength, gl.STATIC_DRAW);
    gl.bufferSubData(gl.ARRAY_BUFFER, 0, vertices);
    gl.bufferSubData(gl.ARRAY_BUFFER, texCoordOffset, texCoords);
    gl.vertexAttribPointer(aPosLoc, 3, gl.FLOAT, false, 0, 0);
    gl.vertexAttribPointer(aTexLoc, 2, gl.FLOAT, false, 0, texCoordOffset);

}
/**
 * Note that CanvasWrapper might also wrap an image instead of a canvas
 * @constructor
 * @param canvas {CindyJS.image}
 */
Note that CanvasWrapper might also wrap an image instead of a canvas
@constructor
@param canvas {CindyJS.image}
Note that CanvasWrapper might also wrap an image instead of a canvas @constructor @param canvas { CindyJS . image }
function CanvasWrapper(canvas, properties) {
    this.canvas = canvas;
    this.properties = properties;
    this.sizeX = canvas.width;
    this.sizeY = canvas.height;
    this.updateInternalTextureMeasures();
    this.ratio = canvas.height / canvas.width;
    this.it = 0;
    this.textures = [];
    this.framebuffers = [];
    this.generation = -1;
    this.bindTexture();
    canvas['drawTo'] = this.drawTo.bind(this);
    canvas['readPixels'] = this.readPixels.bind(this);
    canvas['cdyUpdate'] = this.copyTextureToCanvas.bind(this);
    let rawData = createPixelArray(this.sizeXP * this.sizeYP * 4);
    for (let j = 0; j < 2; j++) {
        this.textures[j] = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, this.textures[j]);
        gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.sizeXP, this.sizeYP, 0, gl.RGBA, getPixelType(), rawData);
        if (properties.mipmap)
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, properties.interpolate ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_LINEAR); 
        else
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, properties.interpolate ? gl.LINEAR : gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, properties.interpolate ? gl.LINEAR : gl.NEAREST);
        if (properties.clamptoedge) {
            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);
        }
        this.framebuffers[j] = gl.createFramebuffer();
        gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffers[j]);
        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.textures[j], 0);
    }
    this.shaderProgram = new ShaderProgram(gl, cgl_resources["copytexture_v"], cgl_resources["copytexture_f"]);
    var posBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
    var vertices = new Float32Array([-1, -1, 0, 1, -1, 0, -1, 1, 0, 1, 1, 0]);
    var aPosLoc = gl.getAttribLocation(this.shaderProgram.handle, "aPos");
    gl.enableVertexAttribArray(aPosLoc);
    var aTexLoc = gl.getAttribLocation(this.shaderProgram.handle, "aTexCoord");
    gl.enableVertexAttribArray(aTexLoc);
    var texCoords = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
    var texCoordOffset = vertices.byteLength;
    gl.bufferData(gl.ARRAY_BUFFER, texCoordOffset + texCoords.byteLength, gl.STATIC_DRAW);
    gl.bufferSubData(gl.ARRAY_BUFFER, 0, vertices);
    gl.bufferSubData(gl.ARRAY_BUFFER, texCoordOffset, texCoords);
    gl.vertexAttribPointer(aPosLoc, 3, gl.FLOAT, false, 0, 0);
    gl.vertexAttribPointer(aTexLoc, 2, gl.FLOAT, false, 0, texCoordOffset);
}
function CanvasWrapper ( canvas , properties ) { this . canvas = canvas ; this . properties = properties ; this . sizeX = canvas . width ; this . sizeY = canvas . height ; this . updateInternalTextureMeasures ( ) ; this . ratio = canvas . height / canvas . width ; this . it = 0 ; this . textures = [ ] ; this . framebuffers = [ ] ; this . generation = - 1 ; this . bindTexture ( ) ; canvas [ 'drawTo' ] = this . drawTo . bind ( this ) ; canvas [ 'readPixels' ] = this . readPixels . bind ( this ) ; canvas [ 'cdyUpdate' ] = this . copyTextureToCanvas . bind ( this ) ; let rawData = createPixelArray ( this . sizeXP * this . sizeYP * 4 ) ; for ( let j = 0 ; j < 2 ; j ++ ) { this . textures [ j ] = gl . createTexture ( ) ; gl . bindTexture ( gl . TEXTURE_2D , this . textures [ j ] ) ; gl . pixelStorei ( gl . UNPACK_ALIGNMENT , 1 ) ; gl . texImage2D ( gl . TEXTURE_2D , 0 , gl . RGBA , this . sizeXP , this . sizeYP , 0 , gl . RGBA , getPixelType ( ) , rawData ) ; if ( properties . mipmap ) gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_MIN_FILTER , properties . interpolate ? gl . LINEAR_MIPMAP_LINEAR : gl . NEAREST_MIPMAP_LINEAR ) ; else gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_MIN_FILTER , properties . interpolate ? gl . LINEAR : gl . NEAREST ) ; gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_MAG_FILTER , properties . interpolate ? gl . LINEAR : gl . NEAREST ) ; if ( properties . clamptoedge ) { 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 ) ; } this . framebuffers [ j ] = gl . createFramebuffer ( ) ; gl . bindFramebuffer ( gl . FRAMEBUFFER , this . framebuffers [ j ] ) ; gl . framebufferTexture2D ( gl . FRAMEBUFFER , gl . COLOR_ATTACHMENT0 , gl . TEXTURE_2D , this . textures [ j ] , 0 ) ; } this . shaderProgram = new ShaderProgram ( gl , cgl_resources [ "copytexture_v" ] , cgl_resources [ "copytexture_f" ] ) ; var posBuffer = gl . createBuffer ( ) ; gl . bindBuffer ( gl . ARRAY_BUFFER , posBuffer ) ; var vertices = new Float32Array ( [ - 1 , - 1 , 0 , 1 , - 1 , 0 , - 1 , 1 , 0 , 1 , 1 , 0 ] ) ; var aPosLoc = gl . getAttribLocation ( this . shaderProgram . handle , "aPos" ) ; gl . enableVertexAttribArray ( aPosLoc ) ; var aTexLoc = gl . getAttribLocation ( this . shaderProgram . handle , "aTexCoord" ) ; gl . enableVertexAttribArray ( aTexLoc ) ; var texCoords = new Float32Array ( [ 0 , 0 , 1 , 0 , 0 , 1 , 1 , 1 ] ) ; var texCoordOffset = vertices . byteLength ; gl . bufferData ( gl . ARRAY_BUFFER , texCoordOffset + texCoords . byteLength , gl . STATIC_DRAW ) ; gl . bufferSubData ( gl . ARRAY_BUFFER , 0 , vertices ) ; gl . bufferSubData ( gl . ARRAY_BUFFER , texCoordOffset , texCoords ) ; gl . vertexAttribPointer ( aPosLoc , 3 , gl . FLOAT , false , 0 , 0 ) ; gl . vertexAttribPointer ( aTexLoc , 2 , gl . FLOAT , false , 0 , texCoordOffset ) ; }
Note that CanvasWrapper might also wrap an image instead of a canvas
 @constructor
 @param canvas {CindyJS.image}
Note that CanvasWrapper might also wrap an image instead of a canvas @constructor @param canvas { CindyJS . image }
//always interpolate between 2 mipmap levels NEAREST_MIPMAP_LINEAR