Transparency lost with getImageData - HTML5 2d Context - javascript

I noticed a strange problem with getImageData; transparency of the image seems to be ignored when the image data is fetched.
Since any image needs to be drawn on to a canvas before its image data can be obtained, I assumed this was a problem with the canvas in question being opaque. But I was wrong, since using the canvas as an argument in drawImage maintains transparency.
Here is how I loaded the image;
var load_image = function(name, url, holder, callback) {
var img = new Image();
img.src = url;
img.addEventListener('load', function(e) {
var canvas = make_canvas(e.target.width, e.target.height);
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(e.target, 0, 0);
holder[name] = {canvas: canvas, ctx: ctx};
delete e.target;
callback.call();
}, false);
};
The callback is simply the draw function, which invokes draw_image to draw the image.
The usual version;
var draw_image = function(ctx, img, sx, sy, w, h, dx, dy) {
ctx.drawImage(img.canvas, sx, sy, w, h, dx, dy, w, h);
};
It simply takes the canvas as an argument for drawImage, and the result is as intended with transparency maintained. Example.
The image data version;
var draw_image = function(ctx, img, sx, sy, w, h, dx, dy) {
var imagedata = img.ctx.getImageData(sx, sy, w, h);
ctx.putImageData(imagedata, dx, dy);
};
This one obtains the image data of the required rectangle from the same canvas as the one used in the usual version, and puts the image data on the canvas I want to draw to. I believe transparency should be maintained, but this is not so. Example. (This is a Dropbox link because of the origin-clean flag.)
Am I wrong in assuming that transparency should be maintained with getImageData? Or am I just using it in a wrong manner?
Either way, help would really be appreciated.

I believe your problem is that putImageData doesn't use a composite operation to blend the source and destination image data. Rather, it's doing a direct write of the red, green, blue, and alpha channels into the canvas. For fully transparent pixels, this means they may or may not appear in the color you expect.
Instead, you can create an intermediate canvas element, draw to that, and then use drawImage() to render that to your target canvas with the appropriate globalCompositeOperation being applied. Mask for putImageData with HTML5 canvas? discusses this issue in more detail.
Update: You can verify that the data you're writing into your "broken" example actually does have transparent data in it by doing ctx.getImageData(230,50,1,1).data. The result is [0,0,0,0] - i.e. a fully transparent pixel.

Related

Why does javascript canvas2d clipping require a path?

I was pulling my hair out over this bug for a while. I wanted to render images in three sections of a canvas, without allowing them to overlap. Basically, I wanted to use canvas.getContext('2d').clip() to keep the images separated. However, the clip only works if I call canvas.getContext('2d').beginPath() after I draw the image.
So this does not work (no clip is applied):
this.draw=function(image, cx, cy, width, height, clip){
var ctx = this.canvas.getContext('2d');
ctx.save();
ctx.rect(clip.x, clip.y, clip.width, clip.height);
ctx.clip();
ctx.fillStyle = "black";
ctx.fillRect(clip.x, clip.y, clip.width, clip.height);
ctx.drawImage(image,cx-width/2,cy-height/2,width,height);
ctx.restore();
return this;
};
But this does:
this.draw=function(image, cx, cy, width, height, clip){
var ctx = this.canvas.getContext('2d');
ctx.save();
ctx.rect(clip.x, clip.y, clip.width, clip.height);
ctx.clip();
ctx.fillStyle = "black";
ctx.fillRect(clip.x, clip.y, clip.width, clip.height);
ctx.drawImage(image,cx-width/2, cy-height/2,width,height);
ctx.beginPath();// <------WITCHCRAFT
ctx.restore();
return this;
};
It was a total accident that I discovered that beginPath() fixes the problem, and I have no idea why. Can anyone explain this to me?
Because clipping requires a path? Perhaps you missed it in the documentation. Here's what MDN documentation says:
The CanvasRenderingContext2D.clip() method of the Canvas 2D API turns the path currently being built into the current clipping path.
(emphasis mine)
The reason it needs a path is because clipping mask can be any arbitrary shape from rectangles to circles to the outline of Pikachu.
For the sake of completeness, here's what the W3C spec says about .clip():
https://www.w3.org/TR/2dcontext/#drawing-paths-to-the-canvas
context . clip()
Further constrains the clipping region to the current path.

How to properly draw and update (special) raw image data on canvas without duplication of data ? (html5)

I don't think the title is very accurate but I can't think of any better.
I'm working with medical images, in these kind of images there isn't definitive pixels values, you have to extract some information and calculate yourself every pixel value. A second particularity is that image resolution can be very huge.
So once I loaded the image into memory I have an ArrayBuffer and some other attributes needed for calculation. Since these images aren't based on any common format I can't use image DOMElement to draw them on a canvas.
Here the 2 related issue I have :
1) I have to use ImageData to draw my image into a canvas, this means I have to iterate over my ArrayBuffer calculate each pixel value and stored them on an ImageData, then I use canvas.putImageData() and the browser will probably iterate over again the ImageData and display pixels. I don't like this because it needs to iterate twice in order to display the image and that every time it's update.
2) As I said images can be very huge so I need to use scaling/slicing, but canvas only provides these features for drawImage not for putImageData. If I don't want to calculate it myself I have to use a buffer canvas, copy the entire image in it and then use drawImage with this buffer canvas as image.
This is bad for 2 reasons :
I'm duplicating data, I have to keep the original ArrayBuffer and a buffer canvas. The buffer canvas can be created and deleted but as I need to update the image on the mouse movement it's better to keep it.
If a change is made on the image like the contrast/brightness I have to re-calculate it for the entire image (buffer canvas creation) even if I'm just displaying a small part of it.
Here is some code :
//image contains raw data: TypedArrayView and some attributes
// size (2828,2320)
var image;
//displayed canvas
// size (500,500)
var canvas;
//buffer canvas
// size (2828,2320)
var buffCanvas;
function drawImage(sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight){
var buffContext = buffCanvas.getContext('2d');
var imageData = buffContext.getImageData(buffCanvas.width,buffCanvas.height);
//Iterate over all the image (2828,2320)
for(var i = 0; i < imageData.data.length; i++ )
//process pixel calculation
imageData[i] = image.buffer[i] + SomeCalculation;
buffContext.putImageData(imageData,0,0);
canvas.getContext('2d').drawImage(buffCanvas,sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
}
function onmousemove(){
//change some image properties
image.contrast = 150;
...
drawImage(sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
}
I'm kinda new on this so I must be missing something because the way I'm doing this can't be productive.
My dream would be to create a view like it's done for ArrayBuffer or to override(not possible) bracket operator or create a fake image element. This could work this way :
function MedicalImage(data, ...){
...
}
MedicalImage.prototype.getPixelValueAt = function(pos){
return data[pos] + SomeCalculation;
}
var img = new MedicalImage();
context.drawImage(img,sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
And browser would call getPixelValueAt() to get the pixel value when needed.

Transparent pixels using HTML5 canvas and getImageData?

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.

How do I clip a image obtained from the getImageData() function?

I'm trying to get the contents of a source canvas, clip it, and then draw it on another canvas. Even though my code works like a charm using a src PNG / new Image() combo, it does not when the source content comes from another canvas.
the code is:
var imgData = src_ctx.getImageData(x, y, w, h);
dest_ctx.putImageData(imgData, x, y+h);
ctx.beginPath(); // Filled triangle
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.lineTo(x2,0);
ctx.lineTo(x1,0);
ctx.clip();
After defining the clipping region, draw the source canvas using drawImage, instead of setting the imagedata.
dest_ctx.beginPath(); // Filled triangle
dest_ctx.moveTo(x1,y1);
dest_ctx.lineTo(x2,y2);
dest_ctx.lineTo(x2,0);
dest_ctx.lineTo(x1,0);
dest_ctx.clip();
// You can control wich region to draw using all the arguments
// drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
dest_ctx.drawImage (srcCanvas, x, y);
getImageData is an almost useless function unless you know what you're doing (ie. checking for hit detection, filtering pixels) but even then it is painfully slow.
I created a JSfiddle example for you fiddle around with (see what I did there!)
The heart of the code is as follows:
1 canvas = document.getElementById('canvas');
2 ctx = canvas.getContext("2d");
3 _canvas=document.createElement('canvas');
4 _ctx = _canvas.getContext("2d");
5 _canvas.width = 200;
6 _canvas.height = 200;
7
8 _ctx.beginPath();
9 _ctx.arc(100, 100, 100,0,Math.PI*2,true);
10 _ctx.clip();
11 _ctx.drawImage(img1, 0, 0);
12
13 ctx.drawImage(_canvas, 1.25 * i * _canvas.width, 500);
Essentially what you are doing is clipping to a cache canvas (_canvas, lines 10 and 11) and drawing that to the main canvas (canvas, line 13).
Note: Ideally you would translate your image so it would be in the center of the clip, but I still can not get my head around translations, especially when coupled with other transformations such as clips.

HTML Canvas: draw image without anti-aliasing

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.

Categories

Resources