Leonid
Leonid

Reputation: 71

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!!!

Upvotes: 2

Views: 2177

Answers (3)

Blindman67
Blindman67

Reputation: 54128

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.

Upvotes: 1

Kaiido
Kaiido

Reputation: 137171

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>

Upvotes: 0

D. Peter
D. Peter

Reputation: 517

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);

Upvotes: 0

Related Questions