I'm developing following case.
Html canvas created by JS.
There are a star image(loaded from png with alpha=0 background) and a diamond image(also loaded from png with alpha=0 background) on canvas.
diamond image is moving toward the star image.
when diamond image is completely behind the star image, like showing star image only and diamond image is completely behind the star image, alert("Hidden");
if more than one pixel of the diamond is shown, alert should not appear.
Since the alpha value of the background of the star is 0, which means star is not a rectangle, it is difficult to detect whether the star image is fully covering the diamond image.
Is there any library or way to detect whether an image is fully covered by other?
Or, does any one know the name of this algorithm so that I can implement in JS?
Thanks for any help!
For objects with unknown shape we can check if object is behind by using pixel check.
Here is a full example on how to do this:
ONLINE DEMO HERE
(GameAlchemist provided a modified version here)
/// basic allocations
var ctx = demo.getContext('2d'),
os = document.createElement('canvas'),
octx = os.getContext('2d'),
w = os.width = demo.width,
h = os.height = demo.height,
/// the images
urlD = 'http://i.imgur.com/U72xIMZ.png',
urlS = 'http://i.imgur.com/n5rgo11.png',
imgD = new Image(),
imgS = new Image(),
cnt = 2,
/// check region (optimized)
rect = [140, 140, 180, 60];
/// load images and when ready, start show
imgD.crossOrigin = imgS.crossOrigin = 'anonymous';
imgD.onload = imgS.onload = function() {
cnt--;
if (cnt === 0) start();
}
imgD.src = urlD;
imgS.src = urlS;
The main function checks the pixels within the region defined above. To optimize we can narrow down the search area. If you need to check if image is visible on the other size the region is simply extended to check that area as well.
The function compares an off-screen canvas with just the foremost image drawn against the "live" canvas where both background and foreground are drawn.
If live canvas = off-screen canvas that means the background image is not visible.
function start() {
octx.drawImage(imgS, (w - imgS.width) * 0.5, 20);
var x = -50,
buffer1 = octx.getImageData(rect[0], rect[1], rect[2], rect[3]).data,
len = buffer1.length;
loop();
function loop() {
ctx.clearRect(0, 0, w, h);
ctx.drawImage(imgD, x, 130);
ctx.drawImage(imgS, (w - imgS.width) * 0.5, 20);
if (compare() === true) {
info.innerHTML = 'Object is behind!';
return;
}
x += 2;
if (x < w) requestAnimationFrame(loop);
}
function compare() {
var buffer2 = ctx.getImageData(rect[0], rect[1], rect[2], rect[3]).data,
i = len - 1;
while(i--) {
if (buffer1[i] !== buffer2[i]) return false
}
return true;
}
}
Related
I am using the glfx.js library in order to use matrix transformation to create the perspective effect for my images. In my app, the system works just like photoshop's smart objects (where you render a flat image and get perspective results after render)
glfx.js uses this function canvas.perspective(before, after) to apply matrix transforms to images, by assigning before and after coordination of the 4 points in an image, and it runs the Matrix command in the background to transform my image.
My issue is that if the resulting image that I want after the transformation applied to it is bigger than the original image (happens if you rotate the image) then the WebGL canvas is going to crop my image.
Look at the following fiddle:
https://jsfiddle.net/human_a/o4yrheeq/
window.onload = function() {
try {
var canvas = fx.canvas();
} catch (e) {
alert(e);
return;
}
// convert the image to a texture
var image = document.getElementById('image');
var texture = canvas.texture(image);
// apply the perspective filter
canvas.draw(texture).perspective( [0,0,774,0,0,1094,774,1094], [0,389,537,0,732,1034,1269,557] ).update();
image.src = canvas.toDataURL('image/png');
// or even if you replace the image with the canvas
// image.parentNode.insertBefore(canvas, image);
// image.parentNode.removeChild(image);
};
<script src="https://evanw.github.io/glfx.js/glfx.js"></script>
<img id="image" crossOrigin="anonymous" src="https://images.unsplash.com/photo-1485207801406-48c5ac7286b2?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=600&fit=max&s=9bb1a18da78ab0980d5e7870a236af88">
Any ideas on how we can make the WebGL canvas fit the rotated image (not make the image smaller) or somehow extract the whole image instead of the cropped one?
More pixels
There is no cover all solution. This is because when you convert from 2D to 3D the size of the projected image can possibly approch infinity (near clipping prevents infinity) so no matter how large you make the image output there is always the possibility of some clipping being applied.
With that caveat out of the way there is a solution for most situations that can avoid clipping. It is very simple, just expand the canvas to hold the additional content.
Find the bounds
To simplify the calculations I have changed the after array to a set of normalised points (they represent the after coords as a scale factor of the image size). I then use the image size to convert to real pixel coordinates. Then from that I workout the min size a texture needs to be to hold both the original image and the projection.
With that info I just create the texture (as a canvas) draw the image. Adjust the befor array if needed (in case some projection points are in negative space) and apply the filter.
So we have an image object that has a width and a height. And you have the projection of those points.
// assuming image has been loaded and is ready
var imgW = image.naturalWidth;
var imgH = image.naturalHeight;
The set the corner array (before)
var before = [0, 0, imgW, 0, 0, imgH, imgW, imgH];
The projection points. To make it easier to deal with I have normalised the projection points to the image size
var projectNorm = [[0, 0.3556], [0.6938, 0], [0.9457, 0.9452], [1.6395, 0.5091]];
If you want to use the absolute coordinates as in the fiddle's after array use the following code. The normalisation is reversed in the snippet after then next, so you can skip the normalisation. I have just updated the answer quickly as I am short of time.
var afterArray = [0,389,537,0,732,1034,1269,557];
projectNorm = [];
for(var i = 0; i < afterArray.length; i+= 2){
afterArray.push([afterArray[i] / before[i], afterArray[i + 1] / before[i + 1]]);
}
Now calculate the size of the projection. This is the important part as it works out the size of the canvas.
var top, left, right, bottom;
top = 0;
left = 0;
bottom = imgH;
right = imgW;
var project = projectNorm.map(p => [p[0] * imgW, p[1] * imgH]);
project.forEach(p => {
top = Math.min(p[1], top);
left = Math.min(p[0], left);
bottom = Math.max(p[1], bottom);
right = Math.max(p[0], right);
});
Now that all the data we need has been gathered we can create a new image that will accommodate the projection. (assuming that the projection points are true to the projection)
var texture = document.createElement("canvas");
var ctx = texture.getContext("2d");
texture.width = Math.ceil(right - left);
texture.height = Math.ceil(bottom - top);
Draw the image at 0,0
ctx.setTransform(1, 0, 0, 1, left, top); // put origin so image is at 0,0
ctx.drawImage(image,0,0);
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
Then flatten the projection point array
var after = [];
project.forEach(p => after.push(...p));
Move all points into positive projection space
after.forEach((p,i) => {
if (i % 2) {
before[i] += -top;
after[i] += -top;
} else {
before[i] += -left;
after[i] += -left;
}
});
The final step is to create the glfx.js objects and apply the filter
// create a fx canvas
var canvas = fx.canvas();
// create the texture
var glfxTexture = canvas.texture(texture);
// apply the filter
canvas.draw(glfxTexture).perspective( before, after ).update();
// show the result on the page
document.body.appendChild(canvas);
Demo
Demo of your snippet using the above method (slight modification for image load)
// To save time typing I have just kludged a simple load image wait poll
waitForLoaded();
function waitForLoaded(){
if(image.complete){
projectImage(image);
}else{
setTimeout(waitForLoaded,500);
}
}
function projectImage(image){
var imgW = image.naturalWidth;
var imgH = image.naturalHeight;
var projectNorm = [[0, 0.3556], [0.6938, 0], [0.9457, 0.9452], [1.6395, 0.5091]];
var before = [0, 0, imgW, 0, 0, imgH, imgW, imgH];
var top, left, right, bottom;
top = 0;
left = 0;
bottom = imgH;
right = imgW;
var project = projectNorm.map(p => [p[0] * imgW, p[1] * imgH]);
project.forEach(p => {
top = Math.min(p[1], top);
left = Math.min(p[0], left);
bottom = Math.max(p[1], bottom);
right = Math.max(p[0], right);
});
var texture = document.createElement("canvas");
var ctx = texture.getContext("2d");
texture.width = Math.ceil(right - left);
texture.height = Math.ceil(bottom - top);
ctx.setTransform(1, 0, 0, 1, left, top); // put origin so image is at 0,0
ctx.drawImage(image,0,0);
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
var after = [];
project.forEach(p => after.push(...p));
after.forEach((p,i) => {
if (i % 2) {
before[i] += -top;
after[i] += -top;
} else {
before[i] += -left;
after[i] += -left;
}
});
// create a fx canvas
var canvas = fx.canvas();
// create the texture
var glfxTexture = canvas.texture(texture);
// apply the filter
canvas.draw(glfxTexture).perspective( before, after ).update();
// show the result on the page
document.body.appendChild(canvas);
}
#image {
display : none;
}
<script src="https://evanw.github.io/glfx.js/glfx.js"></script>
<img id="image" crossOrigin="anonymous" src="https://images.unsplash.com/photo-1485207801406-48c5ac7286b2?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=9bb1a18da78ab0980d5e7870a236af88">
Notes and a warning
Note that the projection points (after array) do not always match the final corner points of the projected image. If this happens the final image may be clipped.
Note This method only works if the before points represent the exterme corners of the original image. If the points (before) are inside the image then this method may fail.
Warning There is no vetting of the resulting image size. Large Images can cause the browser to become sluggish, and sometimes crash. For production code you should do your best to keep the image size within the limits of the device that is using your code. Clients seldom return to pages that are slow and/or crash.
I'm new into HTML5 programming and I wanted to know how to rotate each image when it is added into canvas. Should each of them be placed into a canvas and then rotated? If so how can i add multiple canvas into a single canvas context.
Fiddle : http://jsfiddle.net/G7ehG/
Code
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
// get num of sources
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var sources = {
image1: 'http://farm3.static.flickr.com/2666/3686946460_0acfa289fa_m.jpg',
image2: 'http://farm4.static.flickr.com/3611/3686140905_cbf9824a49_m.jpg'
};
loadImages(sources, function(images) {
context.drawImage(images.image1, 100, 30, 200, 137);
context.drawImage(images.image2, 350, 55, 93, 104);
});
In your comment you mentioned that you know about context.rotate, but you don't want the context to stay rotated. That's not a problem at all. First, calling context.rotate only affects things which are drawn afterwards. Anything drawn before will stay were it was. Second, it can be easily reversed after drawing.
use context.save() to create a snapshot of all current context settings, including current rotation.
use context.rotate(angle) and draw your image. The angle is in Radian. That means a full 360° circle is Math.PI * 2. The point the image will be is rotated around is the current origin of the canvas (0:0). When you want to rotate the image around its center, use context.translate(x, y) to set the origin to where you want the center of the image to be, then rotate, and then draw the image at the coordinates -img.width/ 2, -img.height / 2
use context.restore() to return to your snapshot. Rotation and translation will now be like they were before.
Here is an example function which draws an image rotated by 45° at the coordinates 100,100:
function drawRotated(image, context) {
context.save();
context.translate(100, 100);
context.rotate(Math.PI / 4);
context.drawImage(image, -image.width / 2, -image.height / 2);
context.restore();
}
i draw a canvas(aka canvas 1) with image() then rotate it 25 degree. then i take rotated canvas to make a pattern for another canvas(aka canvas 2). then i draw this . and fill the fillstyle with newly created pattern. i noticed if alert in the middle of below code
finalsleeve_ctx.globalCompositeOperation = "source-atop";
/*****************************************
alert(sleeve.toDataURL('image/png'));
*****************************************/
var pattern = finalsleeve_ctx.createPattern(sleeve, 'repeat');
then firefox gives a correct output but if i dont do alert it does not give me correct output. crome not showing me correct output.
do i need to delay ?
here is what i have tried.
HTML
<div >
<canvas id="sleeve" width=436 height=567></canvas>
<canvas id="finalsleeve" width=436 height=567 ></canvas>
</div>
JS
var sleeve = document.getElementById('sleeve');
var sleeve_ctx = sleeve.getContext('2d');
var finalsleeve = document.getElementById('finalsleeve');
var finalsleeve_ctx = finalsleeve.getContext('2d');
function rotator2(var2,var3)
{
sleeve.width=sleeve.width;
var imageObj_rotator2 = new Image();
imageObj_rotator2.onload = function ()
{
var pattern_rotator2 = sleeve_ctx.createPattern(imageObj_rotator2, "repeat");
sleeve_ctx.fillStyle = pattern_rotator2;
sleeve_ctx.rect(0, 0, sleeve.width, sleeve.height);
sleeve_ctx.rotate(var3 * Math.PI/180);
sleeve_ctx.fill();
}
imageObj_rotator2.src = var2;
}
function drawSleeve()
{
finalsleeve.width = finalsleeve.width;
var imgsleeve = new Image();
imgsleeve.src="http://i.stack.imgur.com/FoqGC.png";
finalsleeve_ctx.drawImage(imgsleeve,0,0);
finalsleeve_ctx.globalCompositeOperation = "source-atop";
alert(sleeve.toDataURL('image/png'));
var pattern = finalsleeve_ctx.createPattern(sleeve, 'repeat');
finalsleeve_ctx.rect(0, 0, sleeve.width, sleeve.height);
finalsleeve_ctx.fillStyle = pattern;
finalsleeve_ctx.fill();
finalsleeve_ctx.globalAlpha = .10;
finalsleeve_ctx.drawImage(imgsleeve, 0, 0);
finalsleeve_ctx.drawImage(imgsleeve, 0, 0);
finalsleeve_ctx.drawImage(imgsleeve, 0, 0);
}
rotator2('http://i.stack.imgur.com/fvpMN.png','25');
drawSleeve();
Here is fiddle.
http://jsfiddle.net/EbBHz/
EDITED
Sorry, I completely misunderstood your question. I just now went back and saw the last question you posted and the goal you are trying to achieve.
To get the functionality you desire you can just create one function, you don't need two. Instead of using a second canvas in the HTML I created a temporary one using javascript.
Here is the simplified and functional code
<canvas id="sleeve" width='436' height='567'></canvas>
var sleeve = document.getElementById('sleeve');
var ctx = sleeve.getContext('2d');
function rotator2(var2, var3) {
// Draw the original sleeves
var imageObj_rotator2 = new Image();
imageObj_rotator2.src = var2;
imageObj_rotator2.onload = function () {
var imgsleeve = new Image();
imgsleeve.src="http://i.stack.imgur.com/FoqGC.png";
ctx.drawImage(imgsleeve,0,0);
// Create a second temporary canvas
var pattern = document.createElement('canvas');
pattern.width = 500;
pattern.height = 500;
var pctx = pattern.getContext('2d');
// Make the pattern that fills the generated canvas
var pattern_rotator2 = pctx.createPattern(imageObj_rotator2, "repeat");
pctx.fillStyle = pattern_rotator2;
pctx.rotate(var3 * Math.PI / 180);
// Fill the generated canvas with the rotated image pattern we just created
pctx.fillRect(0, 0, pattern.width, pattern.height);
// Create a pattern of the generated canvas
var patterned = ctx.createPattern(pattern, "repeat");
// Fills in the non-transparent part of the image with whatever the
// pattern from the second canvas is
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = patterned;
ctx.fillRect(0, 0, sleeve.width, sleeve.height);
}
}
rotator2('http://i.stack.imgur.com/fvpMN.png', '45')
The technique works alright, but only for certain angles. Here is the demo set to 45 degrees. As you can see, there is a problem: part of the sleeve is whited out. However, if you change the degree to 15 like this it works just fine. This is because when the image is being rotated in the created canvas it leaves white space before repeating. To see this issue first hand, change the width and the height of the created canvas to 30 (the default width/height of the image) like this
Note: You may have to click run once the jsfiddle tab is open, canvases don't like generating content when another tab is focused
I tried problem solving the issue including
Making the generated canvas really large (which works but KILLS load
time/crashes page sometimes)
Translating the picture in the generated canvas after rotating it
which didn't work like I had hoped
Coming up with a function to change the width/height to cover the
entire first canvas based on the rotated second-canvas-dimensions, which is by far the most promising, but I don't have the time or desire to work out a good solution
All that being said if the angle HAS to be dynamic you can work on a function for it. Otherwise just use a workaround angle/generated canvas dimensions
final result> Here is a working solution for fill rotated pattern without white at any angle
var sleeve = document.getElementById('sleeve');
var ctx = sleeve.getContext('2d');
function rotator2(var2, var3) {
var x =0;
var y=0;
//pattern size should be grater than height and width of object so that white space does't appear.
var patternSize = sleeve.width+ sleeve.height;
// Draw the original sleeves
var imageObj_rotator2 = new Image();
imageObj_rotator2.src = var2;
imageObj_rotator2.onload = function () {
var imgsleeve = new Image();
imgsleeve.src="http://i.stack.imgur.com/FoqGC.png";
ctx.drawImage(imgsleeve,0,0);
// Create a second temporary canvas
var pattern = document.createElement('canvas');
pattern.width = sleeve.width;
pattern.height = sleeve.height;
var pctx = pattern.getContext('2d');
// Make the pattern that fills the generated canvas
var pattern_rotator2 = pctx.createPattern(imageObj_rotator2, "repeat");
pctx.fillStyle = pattern_rotator2;
//moving rotation point to center of target object.
pctx.translate(x+ sleeve.width/2, y+sleeve.height/2);
pctx.rotate(var3 * Math.PI / 180);
// Fill the generated canvas with the rotated image pattern we just created and expanding size from center of rotated angle
pctx.fillRect(-patternSize/2, -patternSize/2, patternSize, patternSize);
// Create a pattern of the generated canvas
var patterned = ctx.createPattern(pattern, "no-repeat");
// Fills in the non-transparent part of the image with whatever the
// pattern from the second canvas is
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = patterned;
ctx.fillRect(x, y, sleeve.width, sleeve.height);
}
}
rotator2('http://i.stack.imgur.com/fvpMN.png', '50')
Is there any way to check if a selected (x,y) point of a PNG image is transparent?
Building on Jeff's answer, your first step would be to create a canvas representation of your PNG. The following creates an off-screen canvas that is the same width and height as your image and has the image drawn on it.
var img = document.getElementById('my-image');
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);
After that, when a user clicks, use event.offsetX and event.offsetY to get the position. This can then be used to acquire the pixel:
var pixelData = canvas.getContext('2d').getImageData(event.offsetX, event.offsetY, 1, 1).data;
Because you are only grabbing one pixel, pixelData is a four entry array containing the pixel's R, G, B, and A values. For alpha, anything less than 255 represents some level of transparency with 0 being fully transparent.
Here is a jsFiddle example: http://jsfiddle.net/thirtydot/9SEMf/869/ I used jQuery for convenience in all of this, but it is by no means required.
Note: getImageData falls under the browser's same-origin policy to prevent data leaks, meaning this technique will fail if you dirty the canvas with an image from another domain or (I believe, but some browsers may have solved this) SVG from any domain. This protects against cases where a site serves up a custom image asset for a logged in user and an attacker wants to read the image to get information. You can solve the problem by either serving the image from the same server or implementing Cross-origin resource sharing.
Canvas would be a great way to do this, as #pst said above. Check out this answer for a good example:
getPixel from HTML Canvas?
Some code that would serve you specifically as well:
var imgd = context.getImageData(x, y, width, height);
var pix = imgd.data;
for (var i = 0, n = pix.length; i < n; i += 4) {
console.log pix[i+3]
}
This will go row by row, so you'd need to convert that into an x,y and either convert the for loop to a direct check or run a conditional inside.
Reading your question again, it looks like you want to be able to get the point that the person clicks on. This can be done pretty easily with jquery's click event. Just run the above code inside a click handler as such:
$('el').click(function(e){
console.log(e.clientX, e.clientY)
}
Those should grab your x and y values.
The two previous answers demonstrate how to use Canvas and ImageData. I would like to propose an answer with runnable example and using an image processing framework, so you don't need to handle the pixel data manually.
MarvinJ provides the method image.getAlphaComponent(x,y) which simply returns the transparency value for the pixel in x,y coordinate. If this value is 0, pixel is totally transparent, values between 1 and 254 are transparency levels, finally 255 is opaque.
For demonstrating I've used the image below (300x300) with transparent background and two pixels at coordinates (0,0) and (150,150).
Console output:
(0,0): TRANSPARENT
(150,150): NOT_TRANSPARENT
image = new MarvinImage();
image.load("https://i.imgur.com/eLZVbQG.png", imageLoaded);
function imageLoaded(){
console.log("(0,0): "+(image.getAlphaComponent(0,0) > 0 ? "NOT_TRANSPARENT" : "TRANSPARENT"));
console.log("(150,150): "+(image.getAlphaComponent(150,150) > 0 ? "NOT_TRANSPARENT" : "TRANSPARENT"));
}
<script src="https://www.marvinj.org/releases/marvinj-0.7.js"></script>
Building on Brian Nickel's answer, only the wanted single pixel of the source image is drawn onto a 1*1 pixel canvas, which is more efficient than drawing the entire image just to get a single pixel:
function getPixel(img, x, y) {
let canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
canvas.getContext('2d').drawImage(img, x, y, 1, 1, 0, 0, 1, 1);;
let pixelData = canvas.getContext('2d').getImageData(0, 0, 1, 1).data;
return pixelData;
}
With : i << 2
const pixels = context.getImageData(x, y, width, height).data;
for (let i = 0, dx = 0; dx < data.length; i++, dx = i << 2)
{
if (pixels[dx+3] <= 8) { console.log("transparent x= " + i); }
}
Here's a consolidation of a few answers into a runnable snippet that lets you upload a file, hover to preview the RGB value of each pixel, then click to put the RGB in a div.
Pertinent to the original question, the last value (alpha) is the transparency. 0 is fully transparent and 255 is fully opaque.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const input = document
.querySelector('input[type="file"]');
input.addEventListener("change", e => {
const image = new Image();
image.addEventListener("load", e => {
const {width, height} = image;
canvas.width = width;
canvas.height = height;
ctx.drawImage(image, 0, 0);
const {data} = ctx.getImageData(
0, 0, width, height
);
const rgb = (x, y) => {
const i = (x + y * width) * 4;
return data.slice(i, i + 4).join(", ");
};
canvas.addEventListener("mousemove", event => {
const {offsetX: x, offsetY: y} = event;
console.log(rgb(x, y));
});
canvas.addEventListener("click", event => {
const {offsetX: x, offsetY: y} = event;
document.querySelector("div")
.textContent = rgb(x, y);
});
});
image.addEventListener("error", () =>
console.error("failed")
);
image.src = URL
.createObjectURL(event.target.files[0]);
});
.as-console-wrapper {
height: 21px !important;
}
<div>
Upload image and mouseover to preview RGB. Click to select a value.
</div>
<form>
<input type="file">
</form>
<canvas></canvas>
References:
HTML5 Canvas - How to get adjacent pixels position from the linearized imagedata Uint8ClampedArray?
How to upload image into HTML5 canvas
In the creation of my html5 game engine I've been able to do some nice things and get some cool features. On a contract to make a game I've been asked to see if I can remove the background color from sprite images. And I see the pluses with this since we could use jpgs instead on pngs and decrease the size of the images.
Is there any way I can do this with pure javascript? I'd like to be able to do this without using the a canvas element so it can be faster, but if I have to that's okay.
If I have to do that I have another question, I don't want the canvas object to show that I use, can I use a canvas object with document.createElement without applying it to the document? That would be nice since it wouldn't have to be rendered to the webpage. If not I guess I can just move the canvas object to the left out of view.
Lastly do you think a good way to preprocess the images be to send them to a server cgi script and have it return a json pixel array?
Here is the function for floodfill algorithm, it removed the background from an image which is already drawn on the canvas.
In the following code canvas is the HTML5 canvas element and context it canvas.getContext("2d"). You can change the value of colorRange and try the function with different colors. The last line of the function
imageElement.src=canvas.toDataURL("image/png");
is to show the image inside an img tag. So you need an img and a canvas on your page. If you don't want to show the image in img element just remove the last line.
// Remove backgroud without ajax call, can be used in non IE browsers.
function RemoveBackground(){
var startR,startG,startB;
var canvasData;
var canvasWidth=canvas.width;
var canvasHeight=canvas.height;
canvasData=mainContext.getImageData(0,0,canvasWidth,canvasHeight);
startR = canvasData.data[0];
startG = canvasData.data[1];
startB = canvasData.data[2];
if(startR==0&& startG==0 && startR==0) return;
var pixelStack = [[0, 0]];
while(pixelStack.length)
{
var newPos, x, y, pixelPos, reachLeft, reachRight;
newPos = pixelStack.pop();
x = newPos[0];
y = newPos[1];
pixelPos = (y*canvasWidth + x) * 4;
while(y-- >= 0 && matchStartColor(pixelPos,canvasData,startR,startG,startB)){
pixelPos -= canvasWidth * 4;
}
pixelPos += canvasWidth * 4;
++y;
reachLeft = false;
reachRight = false;
while(y++ < canvasHeight-1 && matchStartColor(pixelPos,canvasData,startR,startG,startB))
{
colorPixel(pixelPos,canvasData);
if(x > 0)
{
if(matchStartColor(pixelPos-4,canvasData,startR,startG,startB))
{
if(!reachLeft){
pixelStack.push([x - 1, y]);
reachLeft = true;
}
}
else if(reachLeft)
{
reachLeft = false;
}
}
if(x < canvasWidth-1)
{
if(matchStartColor(pixelPos+4,canvasData,startR,startG,startB))
{
if(!reachRight)
{
pixelStack.push([x + 1, y]);
reachRight = true;
}
}
else if(reachRight)
{
reachRight = false;
}
}
pixelPos += canvasWidth * 4;
}
}
context.putImageData(canvasData, 0, 0);
imageElement.src=canvas.toDataURL("image/png");
}
// Helper function for remove background color.
function matchStartColor(pixelPos,canvasData,startR,startG,startB)
{
var r = canvasData.data[pixelPos];
var g = canvasData.data[pixelPos+1];
var b = canvasData.data[pixelPos+2];
var colorRange=8;
return ((r >= startR-colorRange && r<=startR+colorRange)
&&( g >= startG-colorRange && g<=startG+colorRange)
&&( b >= startB-colorRange && b<= startB+colorRange));
}
// Helper function for remove background color.
function colorPixel(pixelPos,canvasData)
{
canvasData.data[pixelPos] = 255;
canvasData.data[pixelPos+1] = 255;
canvasData.data[pixelPos+2] = 255;
}
Removing background without choppy borders isn't a trivial task, even by hand in image-editing programs. You'll have to implement some sort of antialiasing, at least.
Moreover, it's not a good idea to manipulate an image compressed into a lossy format.
PNG compression is superior (in terms of size) to JPG on simpler images with continuous fill of the same color and certain types of gradients. JPG is only good for heterogeneous images with lots of different colors mixed in unpredictable manner. Like photos. Which one would not expect in game sprites, I guess. And again – JPG is a lossy format.
As for the Canvas element, it doesn't have to be added to the DOM tree at all.
The most naïve algorithm to make a given color transparent would be such: draw the image, get its pixel data, iterate over the data and compare every pixel color with your given color. If it matches, set the alpha to 0.
Canvas API methods you'll need:
drawImage
getImageData
The somewhat tricky in it's simplicity part is the CanvasPixelArray. To check each pixel in such arrays, you do something like that:
for (var i = 0; i < pixelAr.length; i += 4) {
var r = pixelAr[i];
var g = pixelAr[i + 1];
var b = pixelAr[i + 2];
var alpha = pixelAr[i + 3];
}
Personally I would not go down this path. JPEG images are compressed, which means that whatever you define as a background color may change slightly in the compressed file (ie. you'll get the classic JPEG artifacting). Furthermore, you won't be able to support partial transparency unless you define a range for your background color, which in turn makes the editing more complicated. The tradeoff between file size and performance/quality is nowhere near worth it here, in my opinion.
Having said that, the only way you can access the pixel data from an image is by placing it on a canvas first. You can, as you mentioned, work with the canvas off-screen in memory without having to append it to the document.
If I understand your last question correctly, you cannot work with a canvas element on the server side. To work with pixel data on your server, you'd have to use something like PHP's image library.
If all of that doesn't sway you in favor of just using PNG images, here's some sample code that will remove a specified background color from a loaded image:
$(document).ready(function() {
var matte_color = [0, 255, 0, 255]; // rgba: [0, 255];
// Handles the loaded image element by erasing the matte color
// and appending the transformed image to the document.
function handleLoadedImage() {
eraseMatte(); // Eliminate the matte.
// Append the canvas element to the document where it is needed.
document.getElementById("parent_container").appendChild(canvas);
}
// Given the matte color defined at the top, clear all pixels to 100% transparency
// within the loaded sprite.
function eraseMatte() {
canvas.width = sprite.width;
canvas.height = sprite.height;
context.drawImage(sprite, 0, 0); // Draw the image on the canvas so we can read the pixels.
// Get the pixel data from the canvas.
var image_data = context.getImageData(0, 0, sprite.width, sprite.height);
var data = image_data.data; // Obtaining a direct handle is a huge performance boost.
var end = sprite.width * sprite.height * 4; // W x H x RGBA
// Loop over each pixel from the image and clear matte pixels as needed.
for (var i = 0; i < end; i += 4) {
if (data[i] == matte_color[0] && data[i + 1] == matte_color[1] &&
data[i + 2] == matte_color[2] && data[i + 3] == matte_color[3]) { // Color match.
data[i] = data[i + 1] = data[i + 2] = data[i + 3] = 0; // Set pixel to transparent.
}
}
// Put the transformed image data back on the canvas.
context.putImageData(image_data, 0, 0);
}
var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");
var sprite = new Image();
sprite.onload = handleLoadedImage;
sprite.src = "sprite.jpg";
});
You can do that using a canvas, don't know if it is possible without it.
An easy way to achieve what you are trying to do is using the getImageData on your canvas's context:
imgData = myCanvasContext.getImageData(x1, y1, x2, y2);
x1, y1, x2, y2 are the coordenates of the area you want to get data, for the whole use 0, 0, width, height image. The getImageData will return you an ImageData, wich contains an array with rgba values from each pixel. The values will be ordered like this:
http://i.stack.imgur.com/tdHNJ.png
You can manipulate the array imgData.data[index], editing it values and, consequently, editing the image.
Here is a good article about editing images on html5 with canvas:
http://beej.us/blog/2010/02/html5s-canvas-part-ii-pixel-manipulation/
To doesn't show what you are doing, just create the canvas with the css command display:none;
(...)if I can remove the background color from sprite images. And I see the pluses with this since we could use jpgs instead on pngs(...)
I really recommend you to not do that. The jpg compression of the image can make image editing very hard. Removing the background of a jpg image isn't is easy, and it gets harder with the amount of borders on the image. I'm don't think the size that you will economize will compensate the hard work to remove a background from a jpg image.
Not exactly the same, but you can achieve that. I can give you an headstart on this - checkout this jsFiddle. I built this editor using FabricJS.
var canvas = new fabric.Canvas('c');
var imgInstance = new fabric.Image(imgElement);
canvas.add(imgInstance);//initialize the Canvas with the image