This question already has answers here:
Load image from url and draw to HTML5 Canvas
(3 answers)
Closed 4 years ago.
I want to get an image from my database, and show it in a canvas and draw shapes on it. Is there a way to draw image on canvas using image as byte array etc..
A <canvas> is like a regular <img> image with the difference that the Javascript code of the page can actually control the values of the pixels.
You can decide what is the content of the canvas by drawing primitives like lines, polygons, arcs or even other images. You can also set or alter the content by processing the actual red, green, blue and transparency values of every single pixel (using getImageData and putImageData). You can resize the canvas as you wish and drawing on a canvas is quite fast... fast enough to be able to do complex animations with just plain Javascript code.
For example the code to load an image and draw it in a canvas after changing it to black and white and adding a red grid could be:
function canvasTest(img) {
let canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
let ctx = canvas.getContext("2d");
// Copy image pixels to the canvas
ctx.drawImage(img, 0, 0);
// Now let's set R and B channels equal to G
let idata = ctx.getImageData(0, 0, canvas.width, canvas.height);
for (let i=0,n=img.width*img.height*4; i<n; i+=4) {
idata.data[i] = idata.data[i+2] = idata.data[i+1];
}
ctx.putImageData(idata, 0, 0);
// Add a red grid
ctx.beginPath();
for (let y=0; y<img.height; y+=50) {
ctx.moveTo(0, y+0.5); ctx.lineTo(img.width, y+0.5);
}
for (let x=0; x<img.width; x+=50) {
ctx.moveTo(x+0.5, 0); ctx.lineTo(x+0.5, img.height);
}
ctx.strokeStyle = "#F00";
ctx.lineWidth = 1;
ctx.stroke();
// add the final result to page
document.body.appendChild(canvas);
}
The only annoyance is that if you draw an image on a canvas and the image is coming from a different origin from where the page comes from, then the canvas becomes in the general case "tainted" and you cannot access the pixel values any more (getImageData is forbidden). The reason for this limit is security (and the sad fact that security in web application is on the client). In your case since the image database is yours there should be no problem.
Related
For example context.fillText("foobar",30,0); would render the full text "foobar" 30 pixels down, but how could I keep the rightmost 20 pixels, to throw out a random number, from rendering? One solution for this is to render a white box immediately after to hide the rest of foobar. But this solution isn't compatible with other features I want to incorporate. I need a way to really keep the rest of foobar from rendering in the first place. Is this possible in canvas or would I need to use another graphics API?
.clip() allows you to use paths to form a mask. This, combined with the various path methods, would allow you to draw a clipped version of your text.
An example, from the MDN page, uses a circle to mask a rectangle:
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Create circular clipping region
ctx.beginPath();
ctx.arc(100, 75, 50, 0, Math.PI * 2);
ctx.clip();
// Draw stuff that gets clipped
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'orange';
ctx.fillRect(0, 0, 100, 100);
<canvas id="canvas"></canvas>
given two canvas with the same pixel size, where canvas1 contains an arbitrary image (jpg, png or such) and canvas2 contains black and non-black pixels.
what i want to achive: using a third canvas3 i want to clone canvas1 and have every black canvas2 pixel (may including a threshold of blackness) be transparent in canvas3
i already have a working solution like this:
canvas3context.drawImage(canvas1,0,0);
var c3img = canvas3context.getImageData(0,0,canvas3.width,canvas3.height);
var c2img = canvas2context.getImageData(0,0,canvas2.width,canvas2.height);
loop(){
if(c2img i-th,i+1-th,i+2-th pixel is lower than threshold)
set c3img.data[i]=c3img.data[i+1]=c3img.data[i+2]=c3img.data[i+3]=0
}
the problem with above (pseudo) code is, that it is slow
so my question is: anyone can share an idea how to speed this up significantly?
i thought about webgl but i never worked with it - so i have no idea about shaders or the tools or terms needed for this. another idea was that maybe i could convert canvas2 to black&white somehow very fast (not just modifieng every pixel in a loop like above) and work with blend modes to generate the transparent pixels
any help is highly appreciated
answering my own question, i provide a solution for merging an arbitrary image with a black&white image. what im still missing is how to set the alpha channel for just one color of a canvas.
I seperate the question in pieces and answer them each.
Question 1: How to convert a canvas into grayscale without iterating every pixel?
Answer: draw the image on to a white canvas with blend mode 'luminosity'
function convertCanvasToGrayscale(canvas){
var tmp = document.createElement('canvas');
tmp.width = canvas.width;
tmp.height = canvas.height;
var tmpctx = tmp.getContext('2d');
// conversion
tmpctx.globalCompositeOperation="source-over"; // default composite value
tmpctx.fillStyle="#FFFFFF";
tmpctx.fillRect(0,0,canvas.width,canvas.height);
tmpctx.globalCompositeOperation="luminosity";
tmpctx.drawImage(canvas,0,0);
// write converted back to canvas
ctx = canvas.getContext('2d');
ctx.globalCompositeOperation="source-over";
ctx.drawImage(tmp, 0, 0);
}
Question 2: How to convert a grayscale canvas into black&white without iterating every pixel?
Answer: two times color-dodge blend mode with color #FEFEFE will do the job
function convertGrayscaleCanvasToBlackNWhite(canvas){
var ctx = canvas.getContext('2d');
// in case the grayscale conversion is to bulky for ya
// darken the canvas bevore further black'nwhite conversion
//for(var i=0;i<3;i++){
// ctx.globalCompositeOperation = 'multiply';
// ctx.drawImage(canvas, 0, 0);
//}
ctx.globalCompositeOperation = 'color-dodge';
ctx.fillStyle = "rgba(253, 253, 253, 1)";
ctx.beginPath();
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fill();
ctx.globalCompositeOperation = 'color-dodge';
ctx.fillStyle = "rgba(253, 253, 253, 1)";
ctx.beginPath();
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fill();
}
Note: this function assumes that you want black areas left black and every non-black pixel become white! thus a grayscale image which has no black pixel will become completely white
the reason i choose this operation is that it worked better in my case and using only two blend operations means its pretty fast - if you want that more dark pixel be left black and more white pixel become white you can use the commented for loop to darken the image beforehand. thus dark pixel will become black and brighter pixel become darker. as you increase the amount of black pixel's using color-dodge will again do the rest of the job
Question 3: How to merge a Black&White canvas with another canvas without iterating every pixel?
Answer: use 'multiply' blend mode
function getBlendedImageWithBlackNWhite(canvasimage, canvasbw){
var tmp = document.createElement('canvas');
tmp.width = canvasimage.width;
tmp.height = canvasimage.height;
var tmpctx = tmp.getContext('2d');
tmpctx.globalCompositeOperation = 'source-over';
tmpctx.drawImage(canvasimage, 0, 0);
// multiply means, that every white pixel gets replaced by canvasimage pixel
// and every black pixel will be left black
tmpctx.globalCompositeOperation = 'multiply';
tmpctx.drawImage(canvasbw, 0, 0);
return tmp;
}
Question 4: How to invert a Black&White canvas without iterating every pixel?
Answer: use 'difference' blend mode
function invertCanvas(canvas){
var ctx = canvas.getContext("2d");
ctx.globalCompositeOperation = 'difference';
ctx.fillStyle = "rgba(255, 255, 255, 1)";
ctx.beginPath();
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fill();
}
now to 'merge' an canvasimage with a canvasmask one can do
convertCanvasToGrayscale(canvasmask);
convertGrayscaleCanvasToBlackNWhite(canvasmask);
result = getBlendedImageWithBlackNWhite(canvasimage, canvasmask);
regarding performance: obviously those blend modes are much faster than modifieng every pixel and to get a bit faster one can pack all functions together as needed into one function and recycle only one tmpcanvas - but thats left to the reader ^^
as a sidenote: i tested how the size of the resulting png differs when you compare above's getBlendedImageWithBlackNWhite result with the same image but the black areas are made transparent by iterating every pixel and setting the alpha channel
the difference in size is nearly nothing and thus if you dont really need the alpha-mask the information that every black pixel is meant to be transparent may be enough for futher processing
note: one can invert the meaning of black and white using the invertCanvas() function
if you want to know more of why i use those blend modes or how blend modes really work
you should check the math behind them - imho you're not able to develop such functions if ya dont know how they really work:
math behind blend modes
canvas composition standard including a bit math
need an example - spot the difference: http://jsfiddle.net/C3fp4/1/
In my application I need to get some images, process them, and save for later use. So I'm drawing them to a temporary canvas, then getting with getImageData function. But in output transparency is lost...
Here is my code:
var tempCanvas = document.createElement('canvas');
var tempContext = tempCanvas.getContext('2d');
tempContext.drawImage(image, 0, 0);
var imageData = tempContext.getImageData(0, 0, image.width, image.height);
My image has some transparent pixels but after this there are no transparent pixels in imageData how can I solve this issue?
Is there any way to convert Html Image to ImageData, so I can process it and then draw to canvas?
your imageData should contain alpha channel.
However, putImageData will replace the pixel value in the context.
It won't merge it with the previous value of the pixel in the context, it will replace it. So, what you should see is the pixel behind the canvas (in most of cases, the pixel color of the body tag of your html page).
What you have to do:
use a temporaty canvas to get image data is the good way
modifiy the imageData as you need
don't try to put this imageData back in the conetext with a putImageData, it won't behave as you wish
but create a new Image object and give it the imageData as source (yes, it works :))
use drawImage to draw back the image
example of code:
var tempCanvas = document.createElement('canvas');
var tempContext = tempCanvas.getContext('2d');
tempContext.drawImage(image, 0, 0);
var imageData = tempContext.getImageData(0, 0, image.width, image.height);
//modify here the imageData as you need
var img = new Image();
img.src = imageData;
context.drawImage(img, 0, 0); //the true context of the canvas
It should works.
I'm trying to use JavaScript drawImage to draw from a buffer canvas to another in Firefox; I'm calling the draw multiple times per frame using a fairly large canvas. My memory usage shoots through the roof in Firefox, but barely peaks in Chrome. I'm curious about the reason for this behavior and if there's a workaround to free the memory used (I'm assuming) by drawn images after they're no longer needed.
I need to render using globalCompositeOperation = 'source-in', so that's why I'm using this method.
Here's the basic idea:
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
//set height and width of canvas to browser window
var dummyCanvas = document.createElement('canvas');
var dummyctx = dummyCanvas.getContext('2d');
dummyCanvas.width = canvas.width;
dummyCanvas.height = canvas.height;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
//draw some stuff on normal canvas
dummyCtx.clearRect(0, 0, canvas.width, canvas.height);
//draw a polygon on buffer canvas
dummyctx.globalCompositeOperation = 'source-in';
//draw another polygon on buffer canvas
ctx.drawImage(dummyctx.canvas, 0, 0);
//draw some more stuff on normal canvas
}
Is this memory problem just a bug in Firefox? Am I doing something wrong? Are there any workarounds?
Thank you so much for any help!
I notixed that images are somehow piled on each other in a canvas, when I dra more than one. Maybe it would help clearing te canvas, before draing to it again:
context.clearRect(0, 0, canvas.width, canvas.height);
var w = canvas.width;
canvas.width = 1;
canvas.width = w;
I took this from a image resize that I dod not so long ago: http://boxed.hu/articles/html5-client-side-image-resize/
But, this is only a tip - so let me know how it worked.
I had a memory leak problem with the jquery.rotate plugin increasing the memory use on IE and found that although drawing takes up memory, the problem was when the original image was replaced by the manipulated image. Apparently the images were just piling up in memory. The line was:
p.parentNode.replaceChild(canvas, p);
so I changed to use the jQuery function replaceWith() and the memory stopped stacking up after rotating several images:
$(p).replaceWith(canvas);
Looking at the replaceWith function, it actually removes the object (probably in the end uses removeChild and appendChild) and appends the new one to the DOM. My guess is that there's a difference on how the browsers implement replaceChild().
I want to do pixel-true rendering of some images on my Canvas. Right now, I obtain the images through Javascript, so my images are HTMLImageElement instances. I can draw these on the Canvas' rendering context with drawImage. However, this performs anti-aliasing on the image, which I don't want.
There appears to be a lower-level image manipulation method named putImageData, operating on ImageData objects. Does this method perform any anti-aliasing? If not, it's a fine candidate for what I'm looking for, but I haven't found out how I can convert or blit an HTMLImageElement to an ImageData instance.
Any advice would be welcome!
Edit: my original problem was solved, I accidentally had a coordinate that was fractional, which forces anti-aliasing. The conversion-to-image-data question still stands though.
The only way to convert an image into an ImageData object is to draw it to a canvas first, so you'll need to create a temporary canvas, draw the image on it, and get the image data from there.
function imageToImageData(image) {
var canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0);
return ctx.getImageData(0, 0, canvas.width, canvas.height);
}
Note though, that the same-origin policy prevents you from calling getImageData if you draw an image from a different domain to the canvas, so this will only work on images from the same domain as the document. If you need to draw images from other domains, your only option is to call drawImage on the context for you main canvas directly, making sure there are no transformations that will affect the accuracy.