I'm writing a script for work where we have a bunch of images of jewelry 200x200 and the script gets all of the images on a page and creates a canvas and then checks the pixels on the edge for discoloration (they're supposed to be pure white) due to them not being edited correctly.
I started off checking the upper left and upper right corners for accuracy, but now i'm running into items where part of the necklace or whatever can go all the way to the corner or off the side which makes this inaccurate.
How do you recommend I go about this? What I'm doing now is checking if the sum of the rgba values are 1020 for both pixels, and if they aren't, then the image isn't pure white.
There are two possible defects with images: total background discoloration and a grey border around the edge. checking the corner pixels works for the grey border but not for the background if the item extends to the corners/sides.
Check all 4 corners of the image. If at least 1 of the 4 corners is white / 255,255,255 / #FFFFFF, the image is probably okay. (The discolouration should be consistent across the image, right?)
Other than that, there's not a lot you can do to check for the discolouration. However, you could count colours in the image, and check if the colour that occurs most, is in fact white:
<canvas id="canvas" width="300px" height="300px"></canvas>
var canvas = document.getElementById("canvas"),
canvasWidth = canvas.width,
canvasHeight = canvas.height,
c = canvas.getContext("2d"),
img = new Image();
img.src = '/images/favicon.png';
img.onload = drawImage;
function drawImage(){
// Prepare the canvas
var ptrn = c.createPattern(img, 'repeat');
c.fillStyle = "white";
c.fillRect(0,0,canvasWidth,canvasHeight);
c.fillStyle = ptrn;
c.fillRect(0,0,canvasWidth,canvasHeight);
// Get img data
var imgData = c.getImageData(0, 0, canvasWidth, canvasHeight),
data = imgData.data,
colours = {};
// Build an object with colour data.
for (var y = 0; y < canvasHeight; ++y) {
for (var x = 0; x < canvasWidth; ++x) {
var index = (y * canvasWidth + x) * 4,
r = data[index], // Red
g = data[++index], // Green
b = data[++index], // Blue
// a = data[++index], // Alpha
rgb = rgbToHex(r,g,b);
if(colours[rgb]){
colours[rgb]++;
}else{
colours[rgb] = 1;
}
}
}
// Determine what colour occurs most.
var most = {
colour:'',
amount:0
};
for(var colour in colours){
if(colours[colour] > most.amount){
most.amount = colours[colour];
most.colour = colour;
}
}
console.log("Highest occurence:",most,
"\nColours: ",colours);
}
function rgbToHex(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
Related
Using JavaScript I am displaying an array on an html 5 canvas. The program uses c.fillRect() for each value in the array. Everything looks normal until I scale it using c.scale(). After being scaled white lines are visible between the squares. I do know their white because that is the color of the background (When the background changes their color changes too).
Since the squares are 5 units apart I tried setting their width to 5.5 instead of 5; this only remove the white lines when zoom in far enough, but when zooming out the white lines were still there.
This is my code (unnecessary parts removed):
function loop()
{
c.resetTransform();
c.fillStyle = "white";
c.fillRect(0, 0, c.canvas.width, c.canvas.height);
c.scale(scale, scale);
c.translate(xViewportOffset, yViewportOffset);
...
for(var x = 0; x < array.length; x++)
{
for(var y = 0; y < array[x].length; y++)
{
...
c.fillStyle = 'rgb(' + r + ',' + g + ',' + b + ')';
c.fillRect(0 + x * 5, 200 + y * 5, 5, 5);
}
}
...
}
No scaling:
Zoomed in:
Zoomed out:
(the pattern changes depending on the amount of zoom)
Thanks for any help and if any other information is needed please let me know.
Update:
I am using Google Chrome
Version 71.0.3578.98 (Official Build) (64-bit)
This is probably because you are using non-integer values to set the context's scale and/or translate.
Doing so, your rects are not on pixel boundaries anymore but on floating values.
Let's make a simple example:
Two pixels, one at coords (x,y) (11,10) the other at coords (12,10).
At default scale, both pixels should be neighbors.
Now, if we apply a scale of 1.3, the real pixel-coords of the first square will be at (14.3,13) and the ones of the second one at (15.6,13).
None of these coords can hold a single pixel, so browsers will apply antialiasing, which consist in smoothing your color with the background color to give the impression of smaller pixels. This is what makes your grids.
const ctx = small.getContext('2d');
ctx.scale(1.3, 1.3);
ctx.fillRect(2,10,10,10);
ctx.fillRect(12,10,10,10);
const mag = magnifier.getContext('2d');
mag.scale(10,10);
mag.imageSmoothingEnabled = false;
mag.drawImage(small, 0,-10);
/* it is actually transparent, not just more white */
body:hover{background:yellow}
<canvas id="small" width="50" height="50"></canvas><br>
<canvas id="magnifier" width="300" height="300"></canvas>
To avoid this, several solutions, all dependent on what you are doing exactly.
In your case, it seems you'd win a lot by working on an ImageData which would allow you to replace all these fillRect calls to simpler and faster pixel manipulation.
By using a small ImageData, the size of your matrix, you can replace each rect to a single pixel. Then you just need to put this matrix on your canvas and redraw the canvas over itself at the correct scale after disabling the imageSmootingEnabled flag, which allows us to disable antialiasing for drawImage and CanvasPatterns only.
// the original matrix will be 20x20 squares
const width = 20;
const height = 20;
const ctx = canvas.getContext('2d');
// create an ImageData the size of our matrix
const img = ctx.createImageData(width, height);
// wrap it inside an Uint32Array so that we can work on it faster
const pixels = new Uint32Array(img.data.buffer);
// we could have worked directly with the Uint8 version
// but our loop would have needed to iterate 4 pixels every time
// just to draw a radial-gradient
const rad = width / 2;
// iterate over every pixels
for(let x=0; x<width; x++) {
for(let y=0; y<height; y++) {
// make a radial-gradient
const dist = Math.min(Math.hypot(rad - x, rad - y), rad);
const color = 0xFF * ((rad - dist) / rad) + 0xFF000000;
pixels[(y * width) + x] = color;
}
}
// here we are still at 50x50 pixels
ctx.putImageData(img, 0, 0);
// in case we had transparency, this composite mode will ensure
// that only what we draw after is kept on the canvas
ctx.globalCompositeOperation = "copy";
// remove anti-aliasing for drawImage
ctx.imageSmoothingEnabled = false;
// make it bigger
ctx.scale(30,30);
// draw the canvas over itself
ctx.drawImage(canvas, 0,0);
// In case we draw again, reset all to defaults
ctx.setTransform(1,0,0,1,0,0);
ctx.globalCompositeOperation = "source-over";
body:hover{background:yellow}
<canvas id="canvas" width="600" height="600"></canvas>
I have an image of 140 x 210 px that I am trying to divide into three equal vertical stripes, as follows: I need to change the red of all pixels in the left vertical third to 255, the green of all the pixels in the middle vertical to 255, and the blue of all the pixels in the right vertical third to 255. The optical effect this should have is creating a vertical red, green, and blue rainbow over the image.
I am trying to do this with the following code:
var image = new SimpleImage("hilton.jpg");
for (var p of image.values()) {
if (p.getX() < 46) {
p.setRed(255);
}
if (46 < p.getX() < 92) {
p.setGreen(255);
}
if (p.getX() > 92) {
p.setBlue(255);
}
}
print(image);
It adds the red and blue strips just fine to the left and right of the image, respectively. But when I try to add the green strip in the middle, it messes up the whole image, i.e., it turns green the other pixels in the image as well.
I'd like to do this without any library, if possible.
There's a small problem with your code:
if (46 < p.getX() < 92) {
is invalid syntax. That should be:
if (46 < p.getX() && p.getX() < 92) {
That said, without using libraries, you can only do this using an canvas, you can't just change pixels on an image:
// Setup
var canvas = document.getElementById("canvas"),
ctx= canvas.getContext("2d"),
img = new Image(),
w = canvas.width / 3;
// Load image
img.crossOrigin = "anonymous";
img.src = 'http://i190.photobucket.com/albums/z257/americanwildlife/Mammal/Z-ucumari-Valerie-redwolf.jpg';
// When the image has loaded
img.onload = function(){
// Draw it and get it's data
ctx.drawImage(img, 0, 0);
var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height),
data = imgData.data;
// Iterate over all the pixels
for (var i = 0; i < data.length; i += 4) {
// The current pixels' x position
var x = Math.floor(i / 4) % canvas.width;
//Set the pixels' r, g, b channels if x is within certain bounds.
if(x < w)
data[i] = 255; // Red
else if(x >= w && x < w * 2)
data[i+1] = 255; // Green
else
data[i+2] = 255; // Blue
}
// Re-draw the image.
ctx.putImageData(imgData, 0, 0);
}
<canvas id="canvas" width="800px" height="639px"></canvas>
I'm running through this exercise and used the <= operator instead as this ensured a crisp finish.Hope it helps.
var image = new SimpleImage ("smallpanda.png");
var redLeft = image.getWidth()/1/3;
var blueRight = (image.getWidth()/1/3)*2;
//Red
for (var pixel of image.values()){
if (pixel.getX() < redLeft) {
//If the pixel’s x-coordinate is less than one-third of the image’s width, set the pixel’s red value to 255.
pixel.setRed(255);
}
//Green
if (( redLeft <= pixel.getX() ) && (pixel.getX() <= blueRight)) {
//If the pixel’s x-coordinate is between one-third and two-thirds of the image’s width, set the pixel’s green value to 255.
pixel.setGreen(255);
}
//Blue
else{
if (pixel.getX() > blueRight) {
//If the pixel’s x-coordinate is more than two-thirds of the image’s width, set the pixel’s blue value to 255.
pixel.setBlue(255);
}
}
}
print(image);
Awesome posts, and definitely helped me with finishing my project as well. I basically just derived a mixture of the two:
var img = new SimpleImage("duvall.jpg");
for (var pixel of img.values()) {
var redLeft = img.getWidth() / 1/3;
if (pixel.getX() <= redLeft) {
pixel.setRed(255);
}
var blueRight = (img.getWidth() / 1/3) * 2;
if (pixel.getX() > blueRight) {
pixel.setBlue(255);
}
if (pixel.getX() >= redLeft && pixel.getX() <= blueRight) {
pixel.setGreen(255);
}
}
print(img);
try this ~
var img = new SimpleImage ("hilton.jpg");
for (var rgb of img.values()) {
if (rgb.getX() <= img.getWidth()/3) {
rgb.setRed(255);
//only one-third
}
if ((img.getWidth())/3 <= rgb.getX() && (rgb.getX() <= img.getWidth()/3*2)){
rgb.setGreen(255);`enter code here`
//If the pixel’s x-coordinate is between one-third and two-thirds of the
image’s width
}
else if (rgb.getX() > img.getWidth()/3*2)
rgb.setBlue(255);
//more than two-thirds of the image’s width
}
print (img);
var img = new SimpleImage("Hilton.jpg");
//Gives value of each pixel in the image
for(var pixel of img.values())
{
//To divide the given image into 3 parts and to set pixel value to red
if(pixel.getX()<img.getWidth()/3)
{
pixel.setRed(255);
}
//This is to set color of 3rd part of the image to Blue.
else if(pixel.getX()>img.getWidth()*2/3)
{
pixel.setBlue(255);
}
//This is to set color of 2nd part of the image to Green.
else if(img.getWidth()/3<=pixel.getX() && pixel.getX()<=img.getWidth()*2/3)
{
pixel.setGreen(255);
}
}
print(img);
I've created a filter in javascript which inverts the colors of image i.e creates a negative image, now when I run it in the browser it takes time to process and then returns the final negative image. How can I see each pixel of the image being inverted and not just the final inverted image? Instead of waiting for the the code to be implemented on the whole pixel array and then see its effects, I want to see each pixel being changed by the code till the last pixel.
var imgData = ctx.getImageData(0,0,x.width,x.height);
var d = imgData.data;
for (var i=0; i< d.length; i+=4) {
d[i] = 255 - d[i];
d[i+1] = 255 - d[i+1];
d[i+2] = 255 - d[i+2];
}
ctx.putImageData(imgData,0,0);
NEW CODE
invert(d,0);
function invert(d,i){
if(i < d.length){
d[i] = 255 - d[i];
d[i+1] = 255 - d[i+1];
d[i+2] = 255 - d[i+2];
d[i+3] = d[i+3];
//alert(i);
var n=i/4;
var h=parseInt(n/x.width);
var w = n - h*x.width;
ctx.fillStyle='rgba('+d[i]+','+d[i+1]+','+d[i+2]+','+d[i+3]/255+')';
ctx.fillRect(w,h,1,1);
//if(i>91000){alert(i);}
setTimeout(invert(d,i+4),50);
}
else{return ;}
}
You need to use an asynchronous "loop" so you can update some pixels, let the display update the result, then continue.
JavaScript is single threaded so nothing will be updated until the current loop finishes as the thread is occupied with that.
Watching the paint dry
Here is one approach you can use. You define a "batch" size in number of pixels you want to invert (1 is valid if you want to see each pixel, but this can take a long time:
ie. 16.67ms x total number of pixels.
So if you want to display each single pixel update with an image of 640x400 (as in the demo below) then it would take:
640 x 400 x 16.67ms = 4,267,520 ms, or more than an hour
Something to have in mind (the display cannot update faster than per 16.67ms = 60 fps). Below we use 128 pixels per batch.
Live example
Note that the batch value must match the width of the image. F.ex. if your image is 640 pixels wide you can use 1, 5, 10, 20, .. 64, 128 etc.
If you want widths that do not necessarily divide on anything but 1 or fractional values, you have to do a simple calculation to limit the last batch of one line as getImageData() require the arguments to define the area inside an image. Or, just do line by line...
You can also use a "batch" value for vertical tiles (box is probably a better term in that case).
var img = new Image();
img.crossOrigin = "";
img.onload = painter;
img.src = "http://i.imgur.com/Hl3I0cx.jpg";
function painter() {
// setup canvas and image
var canvas = document.querySelector("canvas"),
ctx = canvas.getContext("2d");
// set canvas size = image size
canvas.width = this.naturalWidth; canvas.height = this.naturalHeight;
// draw in image
ctx.drawImage(this, 0, 0);
// prepare loop
var batch = 128,
x = 0, y = 0,
w = canvas.width - batch,
h = canvas.height;
(function asyncUpdate() {
// do one batch only
var idata = ctx.getImageData(x, y, batch, 1), // get a bacth of pixels
data = idata.data,
i = 0, len = data.length;
while(i < len) { // invert the batch
data[i] = 255 - data[i++];
data[i] = 255 - data[i++];
data[i] = 255 - data[i++];
i++
}
ctx.putImageData(idata, x, y); // update bitmap
x += batch;
if (x > w) { // check x pos
x = 0;
y++;
}
if (y < h) { // new batch?
requestAnimationFrame(asyncUpdate); // let display update before next
}
else {
// use a callback here...
console.log("Done");
}
})();
}
<canvas></canvas>
You could context.fillRect each newly changed pixel on the canvas as it's calculated.
Something like this (warning: untested code, may need tweeks!):
var n=i/4;
var y=parseInt(n/canvas.width);
var x=n-y*canvas.width;
context.fillStyle='rgba('+d[i]+','+d[i+1]+','+d[i+2]+','+d[i+3]/255')';
context.fillRect(x,y,1,1);
Here's demo code that first does the inverting and then shows the effect over time:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var nextTime=0;
// a new line of converted image will be displayed
// after this delay
var delay=1000/60*2;
var y=0;
var imgData,d;
var img=new Image();
img.crossOrigin='anonymous';
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/sun.png";
function start(){
cw=canvas.width=img.width;
ch=canvas.height=img.height;
ctx.drawImage(img,0,0);
imgData=ctx.getImageData(0,0,cw,ch);
d=imgData.data;
for (var i=0; i< d.length; i+=4) {
d[i] = 255 - d[i];
d[i+1] = 255 - d[i+1];
d[i+2] = 255 - d[i+2];
}
requestAnimationFrame(animate);
}
function animate(time){
if(time<nextTime){requestAnimationFrame(animate); return;}
nextTime=time+delay;
for(var x=0;x<cw;x++){
var i=(y*cw+x)*4;
ctx.fillStyle='rgba('+d[i]+','+d[i+1]+','+d[i+2]+','+d[i+3]/255+')';
ctx.fillRect(x,y,1,1);
}
if(++y<ch){
requestAnimationFrame(animate);
}else{
ctx.putImageData(imgData,0,0);
}
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>
Say I have this image:
I'd like to recognize the position of the red ball in the image, I could measure the size of the ball(in pixel) in ahead.
I know that I could draw the image to a canvas, then I could get the pixel color data with context.getImageData, but then what should I do? which algorithm sould I use? I'm new to image processing, thanks a lot.
Here's code dedicated to getting that ball position. The output position will logged to the console so have your JS console open! This code has some values in it that you can play with. I chose some that work for your image such as the rough diameter of the ball being 14 pixels and the threshold for each colour component.
I saved the image as "test.jpg" but you can change the code to the correct image path on line 11.
<!DOCTYPE html>
<html>
<body>
<canvas width="800" height="600" id="testCanvas"></canvas>
<script type="text/javascript">
var img = document.createElement('img');
img.onload = function () {
console.log(getBallPosition(this));
};
img.src = 'test.jpg';
function getBallPosition(img) {
var canvas = document.getElementById('testCanvas'),
ctx = canvas.getContext('2d'),
imageData,
width = img.width,
height = img.height,
pixelData,
pixelRedValue,
pixelGreenValue,
pixelBlueValue,
pixelAlphaValue,
pixelIndex,
redThreshold = 128,
greenThreshold = 40,
blueThreshold = 40,
alphaThreshold = 180,
circleDiameter = 14,
x, y,
count,
ballPosition,
closestBallCount = 0,
closestBallPosition;
// Draw the image to the canvas
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0);
// Get the image data
imageData = ctx.getImageData(0, 0, width, height);
pixelData = imageData.data;
// Find the ball!
for (y = 0; y < height; y++) {
// Reset the pixel count
count = 0;
// Loop through the pixels on this line
for (x = 0; x < width; x++) {
// Set the pixel data starting point
pixelIndex = (y * width * 4) + (x * 4);
// Grab the red pixel value
pixelRedValue = pixelData[pixelIndex];
pixelGreenValue = pixelData[pixelIndex + 1];
pixelBlueValue = pixelData[pixelIndex + 2];
pixelAlphaValue = pixelData[pixelIndex + 3];
// Check if the value is within out red colour threshold
if (pixelRedValue >= redThreshold && pixelGreenValue <= greenThreshold && pixelBlueValue <= blueThreshold && pixelAlphaValue >= alphaThreshold) {
count++;
} else {
// We've found a pixel that isn't part of the red ball
// so now check if we found any red data
if (count === circleDiameter) {
// We've found our ball
return {
x: x - Math.floor(circleDiameter / 2),
y: y
};
} else {
// Any data we found was not our ball
if (count < circleDiameter && count > closestBallCount) {
closestBallCount = count;
closestBallPosition = {
x: x - Math.floor(circleDiameter / 2),
y: y
};
}
count = 0;
}
}
}
}
return closestBallPosition;
}
</script>
</body>
</html>
Well i would go and cluster pixels of that color. For example, you could have a look up table where you store red (or in the range of a treshold) pixels (coordinates being the look up key) and an integer value being the cluster id whenever you encounter a pixel without any known red neighbours it starts a new cluster, all other red pixels get the cluster id of a red pixel they are the neighbour of. Depending of you algorithms kernel:
A) XXX B) X
XOX XOX
XXX X
you might need to deal (case B) with a pixel connecting two prior not connected clusters. You would have to replace the cluster id of one of that clusters.
After that you have clusters of pixels. These you can analyse. In case of a round shape i would look for the median in x and y for each cluster and check if all the pixels of that cluster are in the radius.
This will fail if the red ball (or part of it) is in front of another red object. You would than need more complex algorithms.
what i want is to the the HEX or the RGB average value from an image to the another div background this color.
So if i upload an image with a ot of red i get something like #FF0000 just as an example.
Let Me know if this is posible :)
Many thanks.
First, draw the image on a canvas:
function draw(img) {
var canvas = document.createElement("canvas");
var c = canvas.getContext('2d');
c.width = canvas.width = img.width;
c.height = canvas.height = img.height;
c.clearRect(0, 0, c.width, c.height);
c.drawImage(img, 0, 0, img.width , img.height);
return c; // returns the context
}
You can now iterate over the image's pixels. A naive approach for color-detection is to simply count the frequency of each color in the image.
// returns a map counting the frequency of each color
// in the image on the canvas
function getColors(c) {
var col, colors = {};
var pixels, r, g, b, a;
r = g = b = a = 0;
pixels = c.getImageData(0, 0, c.width, c.height);
for (var i = 0, data = pixels.data; i < data.length; i += 4) {
r = data[i];
g = data[i + 1];
b = data[i + 2];
a = data[i + 3]; // alpha
// skip pixels >50% transparent
if (a < (255 / 2))
continue;
col = rgbToHex(r, g, b);
if (!colors[col])
colors[col] = 0;
colors[col]++;
}
return colors;
}
function rgbToHex(r, g, b) {
if (r > 255 || g > 255 || b > 255)
throw "Invalid color component";
return ((r << 16) | (g << 8) | b).toString(16);
}
getColors returns a map of color names and counts. Transparent pixels are skipped. It should be trivial to get the most-frequently seen color from this map.
If you literally want an average of each color component, you could easily get that from the results of getColors, too, but the results aren't likely to be very useful. This answer explains a much better approach.
You can use it all like this:
// nicely formats hex values
function pad(hex) {
return ("000000" + hex).slice(-6);
}
// see this example working in the fiddle below
var info = document.getElementById("info");
var img = document.getElementById("squares");
var colors = getColors(draw(img));
for (var hex in colors) {
info.innerHTML += "<li>" + pad(hex) + "->" + colors[hex];
}
See a working example.
Put image on canvas.
Get 2D context.
Loop through pixels, and store each r,g,b value. If you find the same, increment it once.
Loop through stored r,g,b values and take note of largest r,g,b value.
Convert r,g,b to hex.
This is only possible using the canvas tag as described here :
http://dev.opera.com/articles/view/html-5-canvas-the-basics/#pixelbasedmanipulation
Of course this is only available in newer browsers
You might consider using the convolution filters css allows you to apply. This might be able to get the effect you're going for ( assuming you're wanting to present it back into the html). So you could display the image twice , one convolved.
That being said, doesn't really work if you need the information yourself for some purpose.
For finding that average color:
Put Image on Canvas
Resize image to 1px by 1px
Get the color of the resulting pixel(This pixel will be the calculated average)