Looping through pixels in an image - javascript

My project is to input an image into a canvas tag in an HTML page, and then loop through the pixels and RGBA values of the pixels. While looping through the red values,so every fourth value in the pixel, I want to log the position of the pixels that represent a white pixel. Now, I have the image loading down with some code I got from this blog, http://www.phpied.com/photo-canvas-tag-flip/ .
He has another post in which he gives some code on how to loop through the pixels and log the information I want to log, but I don't understand it, and I don't want to copy his code without knowing what it is I'm doing. So could anybody please either explain the method he's using or perhaps show me another way to do what he's doing? This is the link to the other post http://www.phpied.com/pixel-manipulation-in-canvas/ .

It's straightforward.
All the pixel data for a canvas are stored sequentially in an array.
The first pixel's data occupy array elements #0-3 (red=0/green=1/blue=2/alpha=3).
The second pixel's data occupy array elements #4-7 (red=4/green=5/blue=6/alpha=7).
And so on...
You can load that pixel data by using context.getImageData() and enumerating through the array:
const imgData = context.getImageData(0, 0, canvas.width, canvas.height);
const data = imgData.data;
// enumerate all pixels
// each pixel's r,g,b,a datum are stored in separate sequential array elements
for(let i = 0; i < data.length; i += 4) {
const red = data[i];
const green = data[i + 1];
const blue = data[i + 2];
const alpha = data[i + 3];
}
You can also change those array values and then save the array back to the image using context.putImageData().
// save any altered pixel data back to the context
// the image will reflect any changes you made
context.putImageData(imgData, 0, 0);
The image will then change according to the changes you made to its pixel array.
Each pixel contains 4 components red, green, blue, alpha - each of them is number 0-255. The loop starts from top-left to bottom-right.

I recommend you to use an image processing framework in order to focus on the algorithms instead of manipulating arrays of values. Some frameworks:
fabric.js
processing.js
MarvinJ
In the case of MarvinJ, you can simply loop through pixels iterating column and row coordinates. I use the methods getIntComponentX() to access color components.
for(var y=0; y<image.getHeight(); y++){
for(var x=0; x<image.getWidth(); x++){
var red = image.getIntComponent0(x,y);
var green = image.getIntComponent1(x,y);
var blue = image.getIntComponent2(x,y);
}
}
Therefore you don't need to worry about how the pixel data is represented. In order to check if a pixel is white:
// Is white?
if(red >= 250 && blue >= 250 && green >= 250){
console.log("Pixel at "+x+","+y+" is white");
}
Runnable Example:
var canvas = document.getElementById("canvas");
image = new MarvinImage();
image.load("https://i.imgur.com/eLZVbQG.png", imageLoaded);
function imageLoaded(){
var whitePixels=0;
for(var y=0; y<image.getHeight(); y++){
for(var x=0; x<image.getWidth(); x++){
var red = image.getIntComponent0(x,y);
var green = image.getIntComponent1(x,y);
var blue = image.getIntComponent2(x,y);
var alpha = image.getAlphaComponent(x,y);
// Is white?
if(red >= 250 && green >= 250 && blue >= 250 && alpha > 0){
whitePixels++;
}
}
}
image.draw(canvas);
document.getElementById("result").innerHTML = "white pixels: "+whitePixels;
}
<script src="https://www.marvinj.org/releases/marvinj-0.7.js"></script>
<canvas id="canvas" width="500" height="344"></canvas>
<div id="result"></div>

Related

Get Color Data of HTML5 Canvas

I want to get the percentage of a color in a HTML5 Canvas.
I have a Canvas, where you can draw lines in three colors with the mouse or clean it with a white line.
Now I want to get the percentage of a color in the whole canvas.
I try that:
var c = document.getElementById("can");
var ctx = c.getContext("2d");
var imgData = ctx.getImageData(0, 0, c.width, c.height);
var allPixel=0;
for (var i = 0; i < imgData.data.length; i += 4) {
allPixel+=1;
if(imgData.data[i+2]==255)
blue+=1;
else if (imgData.data[i]==255)
red+=1;
else if (imgData.data[i+1]==green)
green +=1;
}
But this seems to be wrong. The values are strange and always go very high. How can I get the right values of the whole canvas and a color to calculate the percentage.
Can you help me?
And how is it possible with another color instead of the full RGB colors , for example Yellow or white?
Thank you!
White is equal to rgb(255,255,255) so you might be counting that as blue. Also it might be easier to loop through each x and y and get the data, where this code gets the data at exactly one pixel.
var col = ctx.getImageData(x,y,1,1).data
var red = col[0];
var green = col[1];
var blue = col[2];
// increment values

JavaScript canvas, manually cloning a canvas onto another generates a weird pattern

I'm trying to make a text effect similar to the effect found at the bottom of this article
My proposed approach is:
Make two canvasses, one is visible, the other is invisible I use this as a buffer.
Draw some text on the buffer canvas
Loop over getImageData pixels
if pixel alpha is not equal to zero (when there is a pixel drawn on the canvas buffer) with a small chance, ie 2%, draw a randomly generated circle with cool effecs at that pixel on the visible canvas.
I'm having trouble at step 4. With the code below, I'm trying to replicate the text on the second canvas, in full red. Instead I get this weird picture.
code
// create the canvas to replicate the buffer text on.
var draw = new Drawing(true);
var bufferText = function (size, textFont) {
// set the font to Georgia if it isn't defined
textFont = textFont || "Georgia";
// create a new canvas buffer, true means that it's visible on the screen
// Note, Drawing is a small library I wrote, it's just a wrapper over the canvas API
// it creates a new canvas and adds some functions to the context
// it doesn't change any of the original functions
var buffer = new Drawing(true);
// context is just a small wrapper library I wrote to make the canvas API a little more bearable.
with (buffer.context) {
font = util.format("{size}px {font}", {size: size, font: textFont});
fillText("Hi there", 0, size);
}
// get the imagedata and store the actual pixels array in data
var imageData = buffer.context.getImageData(0, 0, buffer.canvas.width, buffer.canvas.height);
var data = imageData.data;
var index, alpha, x, y;
// loop over the pixels
for (x = 0; x < imageData.width; x++) {
for (y = 0; y < imageData.height; y++) {
index = x * y * 4;
alpha = data[index + 3];
// if the alpha is not equal to 0, draw a red pixel at (x, y)
if (alpha !== 0) {
with (draw.context) {
dot(x/4, y/4, {fillColor: "red"})
}
}
}
}
};
bufferText(20);
Note that here, my buffer is actually visible to show where the red pixels are supposed to go compared to where they actually go.
I'm really confused by this problem.
If anybody knows an alternative approach, that's very welcome too.
replace this...
index = x * y * 4;
with...
index = (imageData.width * y) + x;
the rest is good :)

ColorPicker implementation using JavaScript and Canvas

I'm trying to implement ColorPicker using Canvas just for fun. But i seem lost. as my browser is freezing for a while when it loads due to all these for loops.
I'm adding the screenshot of the result of this script:
window.onload = function(){
colorPicker();
}
function colorPicker(){
var canvas = document.getElementById("colDisp"),
frame = canvas.getContext("2d");
var r=0,
g=0,
b= 0;
function drawColor(){
for(r=0;r<255;r++){
for(g=0;g<255;g++){
for(b=0;b<255;b++){
frame.fillStyle="rgb("+r+","+g+","+b+")";
frame.fillRect(r,g,1,1);
}
}
}
}
drawColor();
Currently , i only want a solution about the freezing problem with better algorithm and it's not displaying the BLACK and GREY colors.
Please someone help me.
Instead of calling fillRect for every single pixel, it might be a lot more efficient to work with a raw RGBA buffer. You can obtain one using context.getImageData, fill it with the color values, and then put it back in one go using context.putImageData.
Note that your current code overwrites each single pixel 255 times, once for each possible blue-value. The final pass on each pixel is 255 blue, so you see no grey and black in the output.
Finding a good way to map all possible RGB values to a two-dimensional image isn't trivial, because RGB is a three-dimensional color-space. There are a lot of strategies for doing so, but none is really optimal for any possible use-case. You can find some creative solutions for this problem on AllRGB.com. A few of them might be suitable for a color-picker for some use-cases.
If you want to fetch the rgba of the pixel under the mouse, you must use context.getImageData.
getImageData returns an array of pixels.
var pixeldata=context.getImageData(0,0,canvas.width,canvas.height);
Each pixel is defined by 4 sequential array elements.
So if you have gotten a pixel array with getImageData:
// first pixel defined by the first 4 pixel array elements
pixeldata[0] = red component of pixel#1
pixeldata[1] = green component of pixel#1
pixeldata[2] = blue component of pixel#1
pixeldata[4] = alpha (opacity) component of pixel#1
// second pixel defined by the next 4 pixel array elements
pixeldata[5] = red component of pixel#2
pixeldata[6] = green component of pixel#2
pixeldata[7] = blue component of pixel#2
pixeldata[8] = alpha (opacity) component of pixel#2
So if you have a mouseX and mouseY then you can get the r,g,b,a values under the mouse like this:
// get the offset in the array where mouseX,mouseY begin
var offset=(imageWidth*mouseY+mouseX)*4;
// read the red,blue,green and alpha values of that pixel
var red = pixeldata[offset];
var green = pixeldata[offset+1];
var blue = pixeldata[offset+2];
var alpha = pixeldata[offset+3];
Here's a demo that draws a colorwheel on the canvas and displays the RGBA under the mouse:
http://jsfiddle.net/m1erickson/94BAQ/
A way to go, using .createImageData():
window.onload = function() {
var canvas = document.getElementById("colDisp");
var frame = canvas.getContext("2d");
var width = canvas.width;
var height = canvas.height;
var imagedata = frame.createImageData(width, height);
var index, x, y;
for (x = 0; x < width; x++) {
for (y = 0; y < height; y++) {
index = (x * width + y) * 4;
imagedata.data[index + 0] = x;
imagedata.data[index + 1] = y;
imagedata.data[index + 2] = x + y - 255;
imagedata.data[index + 3] = 255;
}
}
frame.putImageData(imagedata, 0, 0);
};
http://codepen.io/anon/pen/vGcaF

Fill a portion of drawn image in canvas

i drew an image using drawImage() api of html5 canvas. now i want to fill the white spaces of that drew image with different colors (individual box individual color). how can i do it? i am using html5 canvas and jquery.
i want to fill the white spaces with different color and those white spaces are not proper rectangular box.
thanks in advance.
[ changed answer after clarification from questioner ]
Given: an image that has many fully-enclosed transparent areas.
This is a method to fill each transparent area with a different color:
Use context.getImageData to get an array of each canvas pixels [r,g,b,a] value.
Loop thru the array and find the first transparent pixel (the "a" value ==0)
Floodfill the entire transparent area containing that pixel with a new opaque color (replace the r,g,b values with your new color and replace the "a" value ==255).
Repeat step#2 until all the transparent areas have been filled with new unique colors.
To get you started...
William Malone wrote a very nice article on how to get and use canvas's [r,g,b,a] color array.
His article also shows you how to "floodfill" -- replace an existing color with a new color in an entire contiguous area.
In your case, you would replace transparent pixels with a new color.
This is his article:
http://www.williammalone.com/articles/html5-canvas-javascript-paint-bucket-tool/
[Addition to question: insert images into areas ]
You need to make each colorized area transparent again—one at a time
If you save each area's starting pixel from when you originally colorized the areas, you can start with that pixel and re-floodfill an area. This time you’ll set the alpha component of each pixel in that area to 0 (transparent).
As each single area is transparent you use compositing to draw the new image only where the existing pixels are transparent. The composite you want is context.globalCompositeOperation=”source-out”.
These examples show:
After uniquely colorizing each area.
After making 1 area transparent (the top-right area is transparent).
After compositing an image into the transparent area.
//draw some white, green, and blue stripes
for (var i = 0; i < canvas.width; i += 10) {
for (var j = 0; j < canvas.height; j += 10) {
context.fillStyle = (i % 20 === 0) ? "#fff" : ((i % 30 === 0) ? "#0f0" : "#00f");
context.fillRect(i, j, 10, 10);
}
}
var imagedata = context.getImageData(0, 0, canvas.width, canvas.height),
pixels = imagedata.data;
//if found white pixel i.e 255,255,255 changes it to 0,102,204
//you can change it to another color as u wish
for (var offset = 0, len = pixels.length; offset < len; offset += 4) {
if(pixels[offset]==255 &&
pixels[offset+1]==255 &&
pixels[offset+2]==255)
{pixels[offset] = 0; //
pixels[offset + 1] = 102; //
pixels[offset + 2] = 204; //
}
}
context.putImageData(imagedata, 0, 0);

Image editing with javascript

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

Categories

Resources