简体   繁体   中英

How to copy canvas image data to some other variable?

I am working on a small app, that loads user image onto a server, lets him choose one of the filters and gives image back.

I need to somehow save the initial image data with no filters applied.

But as i found out, in JS there is no natural way to copy vars.

I tried using LoDash _.clone() and one of the jQuery functions to do this, but they didn't work.

When I applied a cloned data to image, function putImageData couldn't get the cloned data because of the wrong type.

It seems, that clone functions somehow ignore object types.

Code:

var img = document.getElementById("image");
var canvas = document.getElementById("imageCanvas");
var downloadLink = document.getElementById("download");
canvas.width = img.width;
canvas.height = img.height;

var context = canvas.getContext('2d');
context.drawImage(img, 0, 0, img.width, img.height);
document.getElementById("image").remove();

initialImageData = context.getImageData(0, 0, canvas.width, canvas.height); //initialImageData stores a reference to data, but I need a copy

///////////////////////
normalBtn.onclick = function(){
        if(!(currentState == converterStates.normal)){
             currentState = converterStates.normal;
             //here I need to apply cloned normal data
        }
};

So, what can I do here???

Thanks!!!

Use :

var image = …;
var data = JSON.parse(JSON.stringify(image).data);
var arr = new Uint8ClampedArray(data);
var copy = new ImageData(arr, image.width, image.height);

An ImageData object holds an Uint8ClampedArray which itself holds an ArrayBuffer .

To clone this ArrayBuffer , you can use its slice method, or the one from the TypedArray View you get :

 var ctx = canvas.getContext('2d'); ctx.fillStyle = 'orange'; ctx.fillRect(0,0,300,150); var original = ctx.getImageData(0,0,300,150); var copiedData = original.data.slice(); var copied = new ImageData(copiedData, original.width, original.height); // now both hold the same values console.log(original.data[25], copied.data[25]); // but can be modified independently copied.data[25] = 0; console.log(original.data[25], copied.data[25]); 
 <canvas id="canvas"></canvas> 

But in your case, an easier solution, is to call twice ctx.getImageData .

 var ctx = canvas.getContext('2d'); ctx.fillStyle = 'orange'; ctx.fillRect(0,0,300,150); var original = ctx.getImageData(0,0,300,150); var copied = ctx.getImageData(0,0,300,150); // both hold the same values console.log(original.data[25], copied.data[25]); // and can be modified independently copied.data[25] = 0; console.log(original.data[25], copied.data[25]); 
 <canvas id="canvas"></canvas> 

And an complete example :

 var ctx = canvas.getContext('2d'); var img = new Image(); // keep these variables globally accessible to our script var initialImageData, filterImageData; var current = 0; // just to be able to switch easily img.onload = function(){ // prepare our initial state canvas.width = img.width/2; canvas.height = img.height/2; ctx.drawImage(img, 0,0, canvas.width, canvas.height); // this is the state we want to save initialImageData = ctx.getImageData(0,0,canvas.width,canvas.height); // get an other, independent, copy of the current state filterImageData = ctx.getImageData(0,0,canvas.width,canvas.height); // now we can modify one of these copies applyFilter(filterImageData); button.onclick = switchImageData; switchImageData(); } // remove red channel function applyFilter(image){ var d = image.data; for(var i = 0; i < d.byteLength; i+=4){ d[i] = 0; } } function switchImageData(){ // use either the original one or the filtered one var currentImageData = (current = +!current) ? filterImageData : initialImageData; ctx.putImageData(currentImageData, 0, 0); log.textContent = current ? 'filtered' : 'original'; } img.crossOrigin = 'anonymous'; img.src = 'https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg'; 
 <button id="button">switch imageData</button> <code id="log"></code><br> <canvas id="canvas"></canvas> 

The same with slice :

 var ctx = canvas.getContext('2d'); var img = new Image(); // keep these variables globally accessible to our script var initialImageData, filterImageData; var current = 0; // just to be able to switch easily img.onload = function(){ // prepare our initial state canvas.width = img.width/2; canvas.height = img.height/2; ctx.drawImage(img, 0,0, canvas.width, canvas.height); // this is the state we want to save initialImageData = ctx.getImageData(0,0,canvas.width,canvas.height); // get an other, independent, copy of the current state filterImageData = new ImageData(initialImageData.data.slice(), initialImageData.width, initialImageData.height); // now we can modify one of these copies applyFilter(filterImageData); button.onclick = switchImageData; switchImageData(); } // remove red channel function applyFilter(image){ var d = image.data; for(var i = 0; i < d.byteLength; i+=4){ d[i] = 0; } } function switchImageData(){ // use either the original one or the filtered one var currentImageData = (current = +!current) ? filterImageData : initialImageData; ctx.putImageData(currentImageData, 0, 0); log.textContent = current ? 'filtered' : 'original'; } img.crossOrigin = 'anonymous'; img.src = 'https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg'; 
 <button id="button">switch imageData</button> <code id="log"></code><br> <canvas id="canvas"></canvas> 

The correct way to copy a typed array is via the static function from

eg

var imageData = ctx.getImageData(0,0,100,100);
var copyOfData = Uint8ClampedArray.from(imageData.data); // create a Uint8ClampedArray copy of imageData.data

It will also allow you to convert the type

var copyAs16Bit = Uint16Array.from(imageData.data); // Adds high byte. 0xff becomes 0x00ff

Note that when converting to a smaller type the extra bits are truncated for integers. When converting from floats the value not the bits are copied. When copying between signed and unsigned ints the bits are copied eg Uint8Array to Int8Array will convert 255 to -1. When converting from small int to larger uint eg Int8Array to Uint32Array will add on bits -1 becomes 0xffff

You can also add optional map function

// make a copy with aplha set to half.
var copyTrans = Uint8ClampedArray.from(imageData.data, (d, i) => i % 4 === 3 ? d >> 1 : d);

typedArray.from will create a copy of any array like or iterable objects.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM