I want to create a new ImageData object in code. If I have a Uint8ClampedArray out of which I want to make an image object, what is the best way to do it?
I guess I could make a new canvas element, extract its ImageData and overwrite its data attribute, but that seems like a wrong approach.
It would be great if I could use the ImageData constructor directly, but I can't figure out how to.
This is interesting problem... You can't just create ImageData object:
var test = new ImageData(); // TypeError: Illegal constructor
I have also tried:
var imageData= context.createImageData(width, height);
imageData.data = mydata; // TypeError: Cannot assign to read only property 'data' of #<ImageData>
but as described in MDN data property is readonly.
So I think the only way is to create object and set data property with iteration:
var canvas = document.createElement('canvas');
var imageData = canvas.getContext('2d').createImageData(width, height);
for(var i = 0; i < myData.length; i++){
imageData.data[i] = myData[i];
}
Update:
I have discovered the set method in data property of ImageData, so solution is very simple:
var canvas = document.createElement('canvas');
var imageData = canvas.getContext('2d').createImageData(width, height);
imageData.data.set(myData);
All modern browsers including Chrome, Edge, Firefox and Safari support the ImageData constructor. You can find its definition at MDN.
You can use it like this:
let imgData1 = new ImageData(data, width, height);
let imgData2 = new ImageData(width, height);
For very old browsers you can create this constructor yourself:
function ImageData() {
var i = 0;
if(arguments[0] instanceof Uint8ClampedArray) {
var data = arguments[i++];
}
var width = arguments[i++];
var height = arguments[i];
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext('2d');
var imageData = ctx.createImageData(width, height);
if(data) imageData.data.set(data);
return imageData;
}
The ImageData constructor is finally becoming available in Chrome and Firefox (see the compatibility table on mdn). There are two forms:
var imageData = new ImageData(width, height);
and if you want to build an instance with a UInt8ClampedArray object data:
var imageData = new ImageData(data, width, height); // height is optional
For compatibility reasons, it's best to use createImageData via a canvas 2D context though, as demonstrated in other answers.
There is the createImageData() method. But for this you need the context of an existing canvas.
var myImageData = context.createImageData(cssWidth, cssHeight);
See here for more information.
Related
In general we can convert the HTML elements to string and then we can insert it into DOM later when needed. Similarly, I want to convert the "CANVAS" element to string along with its context properties.
In the following example, I am getting the string value of span tag with outerHTML property. Likewise I want to get the "CANVAS"element along with context properties.
Is there any method or property for this support?
Example code snippets:
var sp=document.createElement("span");
sp.innerHTML = "E2"
var e2 = sp.outerHTML;
$("#test1").append(e2);
var c=document.createElement("CANVAS");
var ctx=c.getContext("2d");
ctx.beginPath();
ctx.moveTo(20,20);
ctx.lineTo(100,20);
ctx.arcTo(150,20,150,70,50);
ctx.lineTo(150,120);
ctx.stroke();
var cn = c.outerHTML;
$("#test2").append(cn);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="test1">
<span>E1</span>
</div>
<div id="test2">
</div>
Seems like you already know how to get dom properties of the canvas object.
Now you only need "context" infos (image data as I understand it)
You can get the image data as a base64 string like this:
function CreateDrawing(canvasId) {
let canvas = document.getElementById(canvasId);
let ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(20,20);
ctx.lineTo(100,20);
ctx.arcTo(150,20,150,70,50);
ctx.lineTo(150,120);
ctx.stroke();
}
function GetDrawingAsString(canvasId) {
let canvas = document.getElementById(canvasId);
let pngUrl = canvas.toDataURL(); // PNG is the default
// or as jpeg for eg
// var jpegUrl = canvas.toDataURL("image/jpeg");
return pngUrl;
}
function ReuseCanvasString(canvasId, url) {
let img = new Image();
img.onload = () => {
// Note: here img.naturalHeight & img.naturalWidth will be your original canvas size
let canvas = document.getElementById(canvasId);
let ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
}
img.src = url;
}
// Create something
CreateDrawing("mycanvas");
// save the image data somewhere
var url = GetDrawingAsString("mycanvas");
// re use it later
ReuseCanvasString("replicate", url);
<canvas id="mycanvas"></canvas>
<canvas id="replicate"></canvas>
In short no!
You should realize the difference between a standard DOM-element and a canvas-element:
A created DOM-element is part of the mark-up language that can be viewed and changed.
In the canvas a vector image is drawn based upon the rules created in script. These rules are not stored in the element as text but as the image and can't be subtracted from the canvas element.
However there are other possibilities. We can get the variables from the ctx-object. But no info about coordinates:
var c=document.createElement("CANVAS");
var ctx=c.getContext("2d");
ctx.beginPath();
ctx.moveTo(20,20);
ctx.lineTo(100,20);
ctx.arcTo(150,20,150,70,50);
ctx.lineTo(150,120);
ctx.stroke();
var ctxInfo = [];
for (ctxKey in ctx)
{
if (Object.prototype.toString.call(ctx[ctxKey]) !== "[object Function]" )
{
ctxInfo.push(ctxKey + " : " + ctx[ctxKey]);
}
}
console.log(ctxInfo);
To transfer from one canvas to the other I would keep a list (array or object) of instructions and write a generic function that applies them.
canvasObject = [["beginPath"], ["moveTo", 20, 20], ["lineTo", 100, 20], ["arcTo", 150, 20, 150, 70, 50], ["lineTo", 150, 120], ["stroke"]];
function createCanvas(cnvsObj)
{
var c = document.createElement("canvas");
var ctx = c.getContext("2d");
cnvsObj.forEach(function(element){
//loop through instructions
ctx[element[0]].apply(ctx, element.slice(1));
});
return c;
}
var a = createCanvas(canvasObject);
document.body.appendChild(a);
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!!!
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.
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>
Ok, so, I'm working on a project in HTML5 and JavaScript. I'm trying to resize a Canvas, but it won't work. I don't think it's my browser, though, because I am using the latest version of FireFox. I've also researched this issue for a while, and I am confident I'm doing this correctly. So, I don't know why it won't work.
Here's my Code:
var level = 1;
var levelImg = undefined;
var width = 0;
var height = 0;
var cnvs = document.getElementById("cnvs").getContext("2d");
width = window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth;
height = window.innerHeight
|| document.documentElement.cleintHeight
|| document.body.cleintHeight;
cnvs.width = width;
cnvs.height = height;
window.onload = function Init(){
levelImg = document.getElementById("level" + level);
setInterval("Draw()", 3);
}
function Draw(){
//Clear the Screen
cnvs.clearRect(0, 0, width, height);
//Draw stuff
DrawLevel();
}
function DrawLevel(){
cnvs.fillRect(0, 0, 10, 10);
}
Any help is appreciated. Thanks.
First correct all the typos in you code.
Use the browser console to detect errors.
Difference between canvas and the context
var cnvs = document.getElementById("cnvs").getContext("2d");
cnvs variable is not a canvas but the context for the canvas.
Canvas is the element and context is the object used to write in the canvas.
To access the canvas you need to do this:
var canvas = document.getElementById("cnvs");
var cnvs = canvas.getContext('2d'); //context
Now when you are trying to change the canvas with, you use canvas, not cnvs.
canvas.width = width;
canvas.height = height;
SetInterval expects a function and a number value that represents milliseconds.
"Draw()" is a string, not a function, and 3 is a really small number between each time the browser draws on canvas. It works, but it's very inefficient.
Other point about setInterval. Avoid it by using requestAnimationFrame() instead.
Take a look here: setTimeout or setInterval or requestAnimationFrame
Defining var levelImg = undefined has no utility. It can be replaced by var levelImg;
Here is my code. I created imageData 2D array in javascript. After I put all pixels into the this array, I want to create image and put these values into the image.
var imageData = new Array(width);
var image = new Image;
for (var i = 0; i < width; i++) {
imageData[i] = new Array(height);
}
image.src = imageData; //Here is the problem. I want to do that.
To create an image from array you can do this:
var width = 400,
height = 400,
buffer = new Uint8ClampedArray(width * height * 4); // have enough bytes
The * 4 at the end represent RGBA which we need to be compatible with canvas.
Fill the buffer with some data, for example:
for(var y = 0; y < height; y++) {
for(var x = 0; x < width; x++) {
var pos = (y * width + x) * 4; // position in buffer based on x and y
buffer[pos ] = ...; // some R value [0, 255]
buffer[pos+1] = ...; // some G value
buffer[pos+2] = ...; // some B value
buffer[pos+3] = 255; // set alpha channel
}
}
When filled use the buffer as source for canvas:
// create off-screen canvas element
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
// create imageData object
var idata = ctx.createImageData(width, height);
// set our buffer as source
idata.data.set(buffer);
// update canvas with new data
ctx.putImageData(idata, 0, 0);
Note that you could use the imageData buffer (here: idata.data) instead of creating your own. Creating your own is really only useful if you use for example floating point values or get the buffer from some other source - setting the buffer as above will take care of clipping and rounding the values for you though.
Now the data in your custom array is copied to the canvas buffer. Next step is to create an image file:
var dataUri = canvas.toDataURL(); // produces a PNG file
Now you can use the data-uri as source for an image:
image.onload = imageLoaded; // optional callback function
image.src = dataUri
You can't make an image.src like that.
A valid dataURL is a base64 encoded string with a type prefix--not an array.
The image data array associated with a canvas is an Uint8ClampedArray--not a normal javascript array.
Here's one way to create a pixel array that you can manipulate:
A Demo: http://jsfiddle.net/m1erickson/956kC/
// create an offscreen canvas
var canvas=document.createElement("canvas");
var ctx=canvas.getContext("2d");
// size the canvas to your desired image
canvas.width=40;
canvas.height=30;
// get the imageData and pixel array from the canvas
var imgData=ctx.getImageData(0,0,40,30);
var data=imgData.data;
// manipulate some pixel elements
for(var i=0;i<data.length;i+=4){
data[i]=255; // set every red pixel element to 255
data[i+3]=255; // make this pixel opaque
}
// put the modified pixels back on the canvas
ctx.putImageData(imgData,0,0);
// create a new img object
var image=new Image();
// set the img.src to the canvas data url
image.src=canvas.toDataURL();
// append the new img object to the page
document.body.appendChild(image);
Create a canvas element of the right size and get its 2D rendering context. You don't have to add this canvas to the document. Use the context to create an ImageData object. Copy the values from your array into the ImageData object. In your case, it might be more efficient to populate the ImageData object in the first place, instead of a separate array. Use the context's putImageData to draw the pixel data. Then, depending on the specific requirements of "Creating image," you might need to serialize the canvas into a data uri, so that you can fill in an img element's src.
I just wrote a little script that resizes uploaded images into canvas thumbnails. While resizing images it slows down the rest of the web page.
I just discovered web workers which I was hoping I could use to alleviate this problem by moving the resizing task into the background.
The MDN article says you can't access the DOM, which is fine, but I was hoping I could still create DOM elements and pass a canvas back.
However, the Image object doesn't even seem to exist from inside my worker thread. Chrome tells me:
Uncaught ReferenceError: Image is not defined resize_image.js:4
So... is there a way to do this without using those elements? I just want to manipulate the image data and then pass it back to the main thread in a usable form. I don't really care about the DOM elements themselves, but I'm not sure if the JS API gives me any other options?
My current resize method, which requires access to DOM elements:
resizeImage: function(file, width, height, callback) {
var reader = new FileReader();
reader.onload = function (fileLoadEvent) {
var img = new Image();
img.onload = function() {
var srcX = 0, srcY = 0, srcWidth = img.width, srcHeight = img.height, ratio=1;
var srcRatio = img.width/img.height;
var dstRatio = width/height;
if(srcRatio < dstRatio) {
ratio = img.width/width;
srcHeight = height*ratio;
srcY = (img.height-srcHeight)/2;
} else {
ratio = img.height/height;
srcWidth = width*ratio;
srcX = (img.width-srcWidth)/2;
}
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext('2d');
ctx.drawImage(this, srcX, srcY, srcWidth, srcHeight, 0, 0, width, height);
callback(canvas);
};
img.src = reader.result;
};
reader.readAsDataURL(file);
},