Is there a way to read transparent pixels from a picture using javascript?
I think, that it could be something similar to what PNG fixes does for IE (reading transparent pixels and applying some stuff, lol). But yes, for every browser..
Ah, would be awesome if it could be achieved without HTML5.
Well this question is actually answered by the dude from GoogleTechTalks in this video on javascript-based game engines.
http://www.youtube.com/watch?feature=player_detailpage&v=_RRnyChxijA#t=1610s
It should start at the point where it is explained.
Edit:
So I will summarize what is told in the video and provide a code-example.
It was a lot tougher than I had expected. The trick is to load your image onto a canvas and then check each pixel if it is transparent. The data is put into a two dimension array. Like alphaData[pixelRow][pixelCol]. A 0 is representing transparency while a 1 is not. When the alphaData array is completed it is put in global var a.
var a;
function alphaDataPNG(url, width, height) {
var start = false;
var context = null;
var c = document.createElement("canvas");
if(c.getContext) {
context = c.getContext("2d");
if(context.getImageData) {
start = true;
}
}
if(start) {
var alphaData = [];
var loadImage = new Image();
loadImage.style.position = "absolute";
loadImage.style.left = "-10000px";
document.body.appendChild(loadImage);
loadImage.onload = function() {
c.width = width;
c.height = height;
c.style.width = width + "px";
c.style.height = height + "px";
context.drawImage(this, 0, 0, width, height);
try {
try {
var imgDat = context.getImageData(0, 0, width, height);
} catch (e) {
netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
var imgDat = context.getImageData(0, 0, width, height);
}
} catch (e) {
throw new Error("unable to access image data: " + e);
}
var imgData = imgDat.data;
for(var i = 0, n = imgData.length; i < n; i += 4) {
var row = Math.floor((i / 4) / width);
var col = (i/4) - (row * width);
if(!alphaData[row]) alphaData[row] = [];
alphaData[row][col] = imgData[i+3] == 0 ? 0 : 1;
}
a=alphaData;
};
loadImage.src = url;
} else {
return false;
}
}
I got errors when running local in Firefox and the try catch statement solved it. Oh I gotta eat...
Edit 2:
So I finished my dinner, I'd like to add some sources I used and wich can be helpful.
https://developer.mozilla.org/En/HTML/Canvas/Pixel_manipulation_with_canvas
Info about the imageData object.
http://blog.nihilogic.dk/2008/05/compression-using-canvas-and-png.html
Even more info about the imageData object and it's use.
http://www.nihilogic.dk/labs/canvascompress/pngdata.js
A really helpful example of the use of imageData, the code I provided resembles this one for a big part.
http://www.youtube.com/watch?v=_RRnyChxijA
Infos on scripting game-engines in javascript, really really interesting.
http://blog.project-sierra.de/archives/1577
Infos about the use of enablePrivilege in firefox.
This is a bit tricky problem, since the only way to access files directly from Javascript is by using FileReader, which is a relatively new feature and not yet supported in most browsers.
However, you could get the desired result by using a canvas. If you have a canvas, you could assign it some distinctive color (such as neon green used in green screens). Then you could insert the image onto canvas and use the method mentioned here to get each individual pixel. Then you could check each pixel's color and see whether that point corresponds to your background color (ergo it's transparent) or does it have some other color (not transparent).
Kind of hackish, but don't think there's anything else you can do with pure JS.
It appears that GameJS can do this and much, much more. I am referencing this SO question for any/all of my knowledge, as I don't claim to actually have any about this topic.
Of course, this is HTML5, and uses the canvas element.
Related
<img src="circle.png" onclick="alert('clicked')"/>
Let's imagine that circle.png is a 400x400 px transparent background image with a circle in the middle.
What I've got now is that the entire image area (400x400px) is clickable. What I would like the have is that only the circle (non transparent pixels) are clickable.
Of course I know that in this example I could use the <map> tag and a circular area, but I'm looking for a general solution which will take into consideration actual image transparency and work for any kind of images (i.e. non regular shapes).
The most complex way I could see is to trace the contour of the image basing on each pixel alpha, convert to a path (maybe simplify) and apply as a map.
Is there a more efficient / straightforward way to do so?
Using the canvas tag, you can determine the color value of a pixel under a given spot. You can use the event data to determine the coordinates, then check for transparency. All that remains is loading the image up into a canvas.
First, we'll take care of that:
var ctx = document.getElementById('canvas').getContext('2d');
var img = new Image();
img.onload = function(){
ctx.drawImage(img,0,0);
};
img.src = [YOUR_URL_HERE];
This first bit grabs the canvas element, then creates an Image object. When the image loads, it is drawn on the canvas. Pretty straightforward! Except... if the image is not on the same domain as the code, you're up against the same-domain policy security. In order to get the data of our image, we'll need the image to be locally hosted. You can also base64 encode your image, which is beyond the scope of this answer. (see this url for a tool to do so).
Next, attach your click event to the canvas. When that click comes in, we'll check for transparency and act only for non-transparent click regions:
if (isTransparentUnderMouse(this, e))
return;
// do whatever you need to do
alert('will do something!');
The magic happens in the function isTransparentUnderMouse, which needs two arguments: the target canvas element (this in the click handler's scope) and the event data (e, in this example). Now we come to the meat:
var isTransparentUnderMouse = function (target, evnt) {
var l = 0, t = 0;
if (target.offsetParent) {
var ele = target;
do {
l += ele.offsetLeft;
t += ele.offsetTop;
} while (ele = ele.offsetParent);
}
var x = evnt.page.x - l;
var y = evnt.page.y - t;
var imgdata = target.getContext('2d').getImageData(x, y, 1, 1).data;
if (
imgdata[0] == 0 &&
imgdata[1] == 0 &&
imgdata[2] == 0 &&
imgdata[3] == 0
){
return true;
}
return false;
};
First, we do some dancing around to get the precise position of the element in question. We're going to use that information to pass to the canvas element. The getImageData will give us, among other things, a data object that contains the RGBA of the location we specified.
If all those values are 0, then we're looking at transparency. If not, there's some color present. -edit- as noted in the comments, the only value we really need to look at is the last, imgdata[3] in the above example. The values are r(0)g(1)b(2)a(3), and transparency is determined by the a, alpha. You could use this same approach to find any color at any opacity that you know the rgba data for.
Try it out here: http://jsfiddle.net/pJ3MD/1/
(note: in my example, I used a base64 encoded image because of the domain security I mentioned. You can ignore that portion of the code, unless you also intend on using base64 encoding)
Same example, with changes to the mouse cursor thrown in for fun: http://jsfiddle.net/pJ3MD/2/
Documentation
Image object on MDN - https://developer.mozilla.org/en/DOM/Image
Tutorial for using images with canvas on MDN - https://developer.mozilla.org/en/Canvas_tutorial/Using_images
Canvas portal on MDN - https://developer.mozilla.org/en/HTML/Canvas
HTML canvas element on MDN (getContext) - https://developer.mozilla.org/en/DOM/HTMLCanvasElement/
CanvasRenderingContext2D on MDN (getImageData) - https://developer.mozilla.org/en/DOM/CanvasRenderingContext2D
Pixel manipulation on MDN - https://developer.mozilla.org/En/HTML/Canvas/Pixel_manipulation_with_canvas/
You can do this using HTML5 canvas. Draw the image in to the canvas, attach a click handler to the canvas, and in the handler, check if the pixel that was clicked on is transparent.
thank you chris for this great answer
I added a few lines to the code to handle with scaling the canvas
so what I do now is creating a canvas of the exact pixel size the image has that is drawn on it. like (for an Image 220px*120px):
<canvas width="220" height="120" id="mainMenu_item"></canvas>
and scale the canvas using css:
#mainMenu_item{
width:110px;
}
and the adjusted isTransparentUnderMouse function looks like:
var isTransparentUnderMouse = function (target, evnt) {
var l = 0, t = 0;
if (target.offsetParent) {
var ele = target;
do {
l += ele.offsetLeft;
t += ele.offsetTop;
} while (ele = ele.offsetParent);
}
var x = evnt.pageX - l;
var y = evnt.pageY - t;
var initialWidth = $(evnt.target).attr('width');
var clientWidth = evnt.target.clientWidth;
x = x * (initialWidth/clientWidth);
var initialHeight = $(evnt.target).attr('height');;
var clientHeight = evnt.target.clientHeight;
y = y * (initialHeight/clientHeight);
var imgdata = target.getContext('2d').getImageData(x, y, 1, 1).data;
if (
imgdata[0] == 0 &&
imgdata[1] == 0 &&
imgdata[2] == 0 &&
imgdata[3] == 0
){
return true;
}
return false;
};
I have a (largish) HTML5 canvas. Its rendering a pictures from a file, using context.drawImage() and this is quite fast. (Note that there are more than one picture on the same canvas).
Now I need to perform some manipulations to the pixels on the canvas, basically I need to perform Alpha Blending which darkens certain areas of the picture. So instead I used this approach.
//create an invisible canvas so that we don't do the actual rendering of the image
var invisibleCanvas = document.createElement('canvas');
invisibleCanvas.width = myWidth;
invisibleCanvas.height = myHeight;
var invContext = invisibleCanvas.getContext('2d');
invContext.drawImage(imageObj, 0, 0, invisibleCanvas.width, invisibleCanvas.height);
var imageData = invContext.getImageData(0, 0, invisibleCanvas.width, invisibleCanvas.height)
var pixelComponents = imageData.data;
var dkBlendingAmount = 0.5;
for (var i = 0; i < pixelComponents.length; i += 4)
{
//there are a few extra checks here to see if we should do the blending or not
pixelComponents[i] = pixelComponents[i] * dkBlendingAmount;
pixelComponents[i+1] = pixelComponents[i+1] * dkBlendingAmount;
pixelComponents[i+2] = pixelComponents[i+2] * dkBlendingAmount;
}
//this is the real place where I want it
context.putImageData(imageData, xOffset, yOffset);
Is there a way to make this faster? Is there a way to get the image data directly from my imageObj rather than having to put it on a canvas, get the data, convert it and put it on another canvas?
I'm currently working on a basic javascript game that has two sprites that are not to be collided together. However, basic bounding box collision won't suffice as there are portions of the sprites that are transparent and wouldn't 'count' as colliding. I found a solution to the problem that I am having, but I can't get it to work. What I would like to do is calculate the transparent portions of the sprites and make sure that if the transparent portions overlap, that there is no collision detected. Here is what I found that solves the problem.
http://blog.weeblog.net/?p=40#comments
/**
* Requires the size of the collision rectangle [width, height]
* and the position within the respective source images [srcx, srcy]
*
* Returns true if two overlapping pixels have non-zero alpha channel
* values (i.e. there are two vissible overlapping pixels)
*/
function pixelCheck(spriteA, spriteB, srcxA, srcyA, srcxB, srcyB, width, height){
var dataA = spriteA.getImageData();
var dataB = spriteB.getImageData();
for(var x=0; x<width; x++){
for(var y=0; y<height; y++){
if( (dataA[srcxA+x][srcyA+y] > 0) && (dataB[srcxB+x][srcyB+y] > 0) ){
return true;
}
}
}
return false;
}
And for calculating the image data:
/**
* creating a temporary canvas to retrieve the alpha channel pixel
* information of the provided image
*/
function createImageData(image){
$('binaryCanvas').appendTo('body');
var canvas = document.getElementById('binaryCanvas');
var ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0);
var canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var imageData = [image.width];
for(var x=0; x<image.width; x++){
imageData[x] = [image.height];
for(var y=0; y<image.height; y++){
var idx = (x + y * image.width) * 4;
imageData[x][y] = canvasData.data[idx+3];
}
}
$("#binaryCanvas").remove();
return imageData;
}
The problem is that I don't know how to implement this solution, or if this is the best solution to my problem. Is this what I'm looking for? And if so, where do I put these methods? The thing that I'm most confused about is what I should be passing to spriteA and spriteB. I've tried passing Images and I've tried passing the imageData returned from the pixelCheck method, but receiving the same error: that the object or image has no method 'getImageData'. What am I doing wrong?
Two things are wrong with this.
You made a function:
function createImageData(image){...}
But what you are calling is:
spriteA.getImageData();
spriteB.getImageData();
The dot notates a property of an object. You were trying to call a function that was never part to the objects. There are some simple fixes.
add the createImageData() function to your constructor :
function Sprite(){
...
this.createImageData = function(image){...};
...
}
or :
Sprite.createImageData = function(image{...};
or just call it correctly:
createImageData(spriteA.image); // or whatever the image is
Second, your function calls for an image parameter, but you aren't supplying one. Simply remember to supply the image when you call it. You could also remove the parameter and get the image from within the function.
Sort of like this:
function Sprite(){
...
this.createImageData = function(){
var image = this.image;
// or just use this.image, or whatever it is
...
}
...
}
Hope this helped.
I'm drawing a simple dynamic canvas and I'm wondering how I can make the .png file in my drawImage method appear like 40 times at different places on my canvas at the same time?
Thanks beforehand! :)
Thank you all very much for your reply! This is as far as I've gotten now:
<script type="text/javascript">
var ctx;
var imgBg;
var imgDrops;
var x = 40;
var y = 0;
function setup() {
var canvas = document.getElementById('canvasRegn');
if (canvas.getContext) {
ctx = canvas.getContext('2d');
setInterval('draw();', 36);
imgBg = new Image();
imgBg.src = 'dimma.jpg';
imgDrops = new Image();
imgDrops.src = 'drop.png';
}
}
function draw() {
drawBackground();
for(var i=0; i <= 40; i++) {
ctx.drawImage (imgDrops, x, y);
y += 3;
if(y > 450)
y = -20;
x=Math.random()*600;
}
}
function drawBackground(){
ctx.drawImage(imgBg, 0, 0);
}
</script>
My problem is now that the images are jumping all over the place... I want them "falling" down slowly from above and coming back around:(
Have a look at this fiddle http://jsfiddle.net/joevallender/D83uC/10/
I made it to explain some basics of canvas to a friend recently. Although I'm using shapes instead of .png files I think the loop you are looking for is the same.
The key bit of code being this loop below
setInterval(function(){
// clear stage
context.clearRect(0, 0, width, height);
for(var i = 0; i < balls.length; i++) {
balls[i].move(balls);
}
}, 1000/FPS)
FPS is a variable, and .move() is a function that calculated new co-ordinates for and then re-draws the ball object.
I think it might simply not clearing the 'stage' context.clearRect(0, 0, width, height);
EDIT Perhaps that example had too much going on in it to be useful.
Please see a much earlier version http://jsfiddle.net/joevallender/D83uC/2 that simple animates the ball. The main point remains though, you need to clear the canvas if you don't want the 'trails'
Think of canvas like windows paint, not flash. The things you have drawn aren't editable objects. You need to redraw the whole thing each time. (Unless you use a JS library that makes things seem more like flash - but I'm guessing you want to learn without helper libraries at first)
As I said, I was explaining canvas to someone recently and you can see the various stages between the two links I've sent you by changing the number on the end of the URL http://jsfiddle.net/joevallender/D83uC/3
http://jsfiddle.net/joevallender/D83uC/4
etc.
EDIT2
Or if I've misunderstood, post a jsfiddle and let us know what is wrong with it
This is what you need: http://edumax.org.ro/extra/new/imagetut/
You can get the code here: http://edumax.org.ro/extra/new/imagetut/script.js
The relevant part is this:
function draw_image(){
//draw one image code
}
window.onload = function () {
for (var i=0;i<10;i++){
for (var j=0;j<10;j++){
draw_image(10+i*40, 10+j*40, 40, 40);
}
}
}
This code only explains the concept, it will not work by itself, for a full version check the link above.
I'm interested in moving a pixel (eventually all of them) from an image drawn on canvas. This is the sample code i'm working off and i believe it does some pretty standard stuff in terms of drawing the image:
var images = [ // predefined array of used images
'http://distilleryimage6.instagram.com/928c49ec07d411e19896123138142014_7.jpg'
];
var iActiveImage = 0;
$(function(){
// drawing active image
var image = new Image();
image.onload = function () {
ctx.drawImage(image, 0, 0, image.width, image.height); // draw the image on the canvas
}
image.src = images[iActiveImage];
// creating canvas object
canvas = document.getElementById('panel');
ctx = canvas.getContext('2d');
console.log(ctx);
var imageData = ctx.getImageData(200, 150, 1, 1);
var pixel = imageData.data;
});
So i have this but could someone point me in the right direction of doing something like picking a pixel at random from the image and physically moving it somewhere else on the page? Is this possible?
Thanks
I'm not sure what you mean by "physically moving it", but you can use ctx.putImageData() to apply the pixel elsewhere inside the canvas.
var imageData = ctx.getImageData(200, 150, 1, 1);
var pixel = imageData.data;
// You can even get or set the color or alpha of the pixel. (values between 0-255)
var r = pixel[0];
var g = pixel[1];
var b = pixel[2];
var a = pixel[3];
ctx.putImageData(imageData, x, y); // Where x y are the new coordinates.
Also, you should put all this imageData manipulation inside the onload function, because in your example, the image is still not loaded when you call ctx.getImageData(), so you're manipulating blank pixels.
Note also that for security reason, you cannot use getImageData() on an image loaded from a different domain. So I think your example will throw an exception of type Uncaught Error: SECURITY_ERR: DOM Exception 18, because the image is loaded from Instagram.
I think this tutorial might help you:
https://developer.mozilla.org/en/Canvas_tutorial/Basic_animations