Reputation: 71
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
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
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
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