I am trying to set the colors of all the pixels on the canvas. I'm pretty sure I know how it works: first 4 values in canvas.getContext("2d").getImageData.data refer to the r, g, b, and a of the first pixel, second 4 go to second pixel, and so on. I would like to know what is wrong with this code:
function draw()
{
var cnvs = document.getElementById("CanvasFlynn"); //Assume it's 2x2 pixels
var cont = cnvs.getContext("2d");
var imdt = cont.getImageData(0,0,2,2);
var r = [ 255 , 0 , 0 , 255 ];
var g = [ 0 , 255 , 0 , 255 ];
var b = [ 0 , 0 , 255 , 255 ];
var a = [ 1 , 1 , 1 , 1 ];
var index = 0;
for ( var i = 0 ; i < imdt.data.length ; i++ )
{
index = 4*i;
imdt.data[index] = r[i];
imdt.data[index+1] = g[i];
imdt.data[index+2] = b[i];
imdt.data[index+3] = a[i];
}
}
If anyone could tell me why this doesn't change the canvases pixel colors, that would rock (I don't actually use a 2x2 cavas, that was just to serve as an example).
There's a bunch of issues
cont.getImageData is a function not a property
imdt isn't the canvas's image data, it's a copy of the canvas's image data.
You need to call cont.putImageData to copy the data back into the canvas
imdt.data.length is a count of bytes not pixels. There are 4 bytes per pixel.
alpha is in the range of 0 to 255 in image data
function draw()
{
var cnvs = document.getElementById("CanvasFlynn"); //Assume it's 2x2 pixels
var cont = cnvs.getContext("2d");
var imdt = cont.getImageData(0, 0, 2, 2);
var r = [ 255 , 0 , 0 , 255 ];
var g = [ 0 , 255 , 0 , 255 ];
var b = [ 0 , 0 , 255 , 255 ];
var a = [ 255 , 255 ,255 ,255 ];
var index = 0;
for ( var i = 0 ; i < imdt.data.length / 4 ; i++ )
{
index = 4*i;
imdt.data[index] = r[i];
imdt.data[index+1] = g[i];
imdt.data[index+2] = b[i];
imdt.data[index+3] = a[i];
}
cont.putImageData(imdt, 0, 0);
}
draw();
<p>Note the 2x2 canvas will be stretched to 100x100 and may be bilinear filtered</p>
<canvas id="CanvasFlynn" width="2" height="2"
style="width: 100px; height: 100px; border: 1px solid black; image-rendering: pixelated;"></canvas>
note that it would arguable be faster to treat the data as 32bit values so then there is one value per pixel
function draw()
{
var cnvs = document.getElementById("CanvasFlynn"); //Assume it's 2x2 pixels
var cont = cnvs.getContext("2d");
var imdt = cont.getImageData(0, 0, 2, 2);
// make a Uint32 view of the image data
var pixels = new Uint32Array(imdt.data.buffer);
var r = 0xFF0000FF;
var g = 0xFF00FF00;
var b = 0xFFFF0000;
var y = 0xFF00FFFF;
pixels[0] = r;
pixels[1] = g;
pixels[2] = b;
pixels[3] = y;
cont.putImageData(imdt, 0, 0);
}
draw();
<p>Note the 2x2 canvas will be stretched to 100x100 and may be bilinear filtered</p>
<canvas id="CanvasFlynn" width="2" height="2"
style="width: 100px; height: 100px; border: 1px solid black; image-rendering: pixelated;"></canvas>
Related
I want to create an element, where the end-user can enter its information, and the result is shown in a pie chart.
I have borrowed a pre-made pie chart, and when inserting the data as fixed numbers, there is no issue, it shows the right distribution.
However when adding a user input in this example a range type to substitute one of the data points, it is not showing the results. What am I missing here?
<canvas id="test_canvas" width="400" height="250"></canvas>
<input id="danmark" type="range" min="1000" max ="3000" step="100" placeholder ="2500" value="2500" oninput="myFunction()" >
<script>
// pie chart drawings
function drawPieChart( data, colors, title ){
var canvas = document.getElementById( "test_canvas" );
var context = canvas.getContext( "2d" );
// get length of data array
var dataLength = data.length;
// declare variable to store the total of all values
var total = 0;
// calculate total
for( var i = 0; i < dataLength; i++ ){
// add data value to total
total += data[ i ][ 1 ];
}
// declare X and Y coordinates of the mid-point and radius
var x = 100;
var y = 100;
var radius = 100;
// declare starting point (right of circle)
var startingPoint = 0;
for( var i = 0; i < dataLength; i++ ){
// calculate percent of total for current value
var percent = data[ i ][ 1 ] * 100 / total;
// calculate end point using the percentage (2 is the final point for the chart)
var endPoint = startingPoint + ( 2 / 100 * percent );
// draw chart segment for current element
context.beginPath();
// select corresponding color
context.fillStyle = colors[ i ];
context.moveTo( x, y );
context.arc( x, y, radius, startingPoint * Math.PI, endPoint * Math.PI );
context.fill();
// starting point for next element
startingPoint = endPoint;
// draw labels for each element
context.rect( 220, 25 * i, 15, 15 );
context.fill();
context.fillStyle = "black";
context.fillText( data[ i ][ 0 ] + " (" + data[ i ][ 1 ] + ")", 245, 25 * i + 15 );
}
// draw title
context.font = "20px Arial";
context.textAlign = "center";
context.fillText( title, 100, 225 );
}
function myFunction () {
var Danmark = document.getElementbyId('danmark').value;
var Norge = 850;
var Sverige = 2400;
var Finland = 1000;
var Holland = 3000;
}
// the data, which needs to be calculated based on user input variables.
var data = [ [ "Danmark", Danmark ], [ "NL", Holland ], [ "NO", Norge ], [ "SE", Sverige ], [ "FI", Finland ] ];
var colors = [ "red", "orange", "green", "blue", "purple" ];
// using the function
drawPieChart( data, colors, "Sales" );
</script>
```
You have a typo in
var Danmark = document.getElementbyId('danmark').value;
It should be getElementById not getElementbyId.
Another thing. When you get value from input or textarea it returns string value by default. You have to convert that value in the number format.
let Danmark = document.getElementById('danmark').value;
let converted = parseInt(Danmark);
function checkVals() {
console.log(typeof Danmark);
console.log(typeof converted);
}
<textarea id="danmark" onchange="checkVals()"></textarea>
At the beginning of your drawPieChart function, under your context variable you'll need to add context.clearRect(0, 0, canvas.width, canvas.height);. This will clear the canvas for the next draw, otherwise it will just draw on top of what's already there and you won't be able to see the changes.
So it will look like this:
function drawPieChart( data, colors, title ){
var canvas = document.getElementById( "test_canvas" );
var context = canvas.getContext( "2d" );
context.clearRect(0, 0, canvas.width, canvas.height);
Hello I'm using NodeJs and a package named "Jimp".
I'm trying to get the Red, Green, Blue, and Alpha values for each pixel but am having trouble.
Here's my code:
Jimp.read("https://i.imgur.com/eOu89DY.png").then((image) => {
console.log(image)
for (var y = 0; y < image.bitmap.height; y = y + 3) {
for (var x = 0; x < image.bitmap.width; x = x + 3) {
//--Returns the whole bitmap buffer
console.log(red)
var red = image.bitmap.data;
}
}
and here's the example usage from https://www.npmjs.com/package/jimp :
image.scan(0, 0, image.bitmap.width, image.bitmap.height, function(x, y, idx) {
// x, y is the position of this pixel on the image
// idx is the position start position of this rgba tuple in the bitmap Buffer
// this is the image
var red = this.bitmap.data[idx + 0];
var green = this.bitmap.data[idx + 1];
var blue = this.bitmap.data[idx + 2];
var alpha = this.bitmap.data[idx + 3];
// rgba values run from 0 - 255
// e.g. this.bitmap.data[idx] = 0; // removes red from this pixel
});
Using my code, how could I get the red, green, blue values using variables from the buffer? How do I define IDX like the example usage from the npmjs site???
If you look down a little in the linked page.
Alternatively, you can manipulate individual pixels using the
following these functions:
image.getPixelColor(x, y); // returns the colour of that pixel e.g. 0xFFFFFFFF
...
...
Two static helper functions exist to convert RGBA values into single integer (hex) values:
Jimp.rgbaToInt(r, g, b, a); // e.g. converts 255, 255, 255, 255 to
Jimp.intToRGBA(hex); // e.g. converts 0xFFFFFFFF to {r: 255, g: 255, b: 255, a:255}
i am trying to create image filters using WebGL. i have found this library WebGLImageFilter.The filter effect I am trying to get is Rise Effect
from CSSGram. Here is the source code for that filter.
it basically does this:
apply brightness(1.05)
apply sepia(0.2)
apply contrast(0.9)
apply saturate(0.9)
In the give order, so i replicate this using the library i mentioned.
const image = new Image();
image.crossOrigin = "anonymous";
image.src = "https://i.imgur.com/TSiyiJv.jpg";
image.onload = function() {
try {
var filter = new WebGLImageFilter();
}
catch( err ) {
console.log(err)
}
filter.addFilter('brightness',1.05);
filter.addFilter('sepia',0.2);
filter.addFilter('contrast',0.9);
filter.addFilter('saturation',0.9);
var filteredImage = filter.apply(image);
document.body.appendChild(filteredImage);
}
<script src="https://cdn.rawgit.com/phoboslab/WebGLImageFilter/e0eee0cd/webgl-image-filter.js"></script>
But this gives a very different image effect than the mentioned one.so I tried to apply each effect individually and each of them works perfectly but when I combine them I get a different effect than the one I am trying to achieve. what could be the reason?
The library you're using doesn't take the same values as input.
For example CSS saturation takes a value 0 to 1 for how saturated. In other words 0 = no saturation, 1 = full saturation where as the library you linked to takes a positive or negative value for how much to change the saturation. In other words 0 = don't change the saturation. 1 = apply 1 amount of a saturation (amount being however much the library uses by default) and -1 remove one unit of saturation
Also AFAIK the sepia filter doesn't take an input. No matter what value you pass in for sepia it just makes the picture the same amount of sepia.
const image = new Image();
image.crossOrigin = "anonymous";
image.src = "https://i.imgur.com/TSiyiJv.jpg";
image.onload = function() {
for (var i = 0; i < 1; i += 0.25) {
var filter = new WebGLImageFilter();
filter.addFilter('sepia', i);
var filteredImage = filter.apply(image);
document.body.appendChild(filteredImage);
}
}
canvas { width: 100px; margin: 5px }
<script src="https://cdn.rawgit.com/phoboslab/WebGLImageFilter/e0eee0cd/webgl-image-filter.js"></script>
Looking inside the library most of the filters are based on a 5x4 color matrix where the identity (the matrix that leaves the colors as they are) is
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0
For sepia it's just hard coded to
0.393, 0.7689999, 0.18899999, 0, 0,
0.349, 0.6859999, 0.16799999, 0, 0,
0.272, 0.5339999, 0.13099999, 0, 0,
0,0,0,1,0
The shader being used looks like this
precision highp float;
varying vec2 vUv;
uniform sampler2D texture;
uniform float m[20];
void main(void) {
vec4 c = texture2D(texture, vUv);
gl_FragColor.r = m[0] * c.r + m[1] * c.g + m[2] * c.b + m[3] * c.a + m[4];
gl_FragColor.g = m[5] * c.r + m[6] * c.g + m[7] * c.b + m[8] * c.a + m[9];
gl_FragColor.b = m[10] * c.r + m[11] * c.g + m[12] * c.b + m[13] * c.a + m[14];
gl_FragColor.a = m[15] * c.r + m[16] * c.g + m[17] * c.b + m[18] * c.a + m[19];
}
Which if I understand correctly, reading cross the rows for sepia, means
new red = 39% red, 77% green, 19% blue
new green = 35% red, 69% green, 17% blue
new blue = 27% red, 53% green, 13% blue
new alpha = alpha
So to actually be able to set the amount you need have it be the identity matrix when amount = 0 and the sepia matrix when amount = 1. Fortunately it looks like there's a colorMatrix filter where you can pass in your own matrix. Let's try it
const identity = [
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0,
];
const sepia = [
0.393, 0.7689999, 0.18899999, 0, 0,
0.349, 0.6859999, 0.16799999, 0, 0,
0.272, 0.5339999, 0.13099999, 0, 0,
0,0,0,1,0,
];
const image = new Image();
image.crossOrigin = "anonymous";
image.src = "https://i.imgur.com/TSiyiJv.jpg";
image.onload = function() {
for (var i = 0; i <= 1; i += 0.25) {
var filter = new WebGLImageFilter();
filter.addFilter('colorMatrix', mix(identity, sepia, i));
var filteredImage = filter.apply(image);
document.body.appendChild(filteredImage);
}
}
function mix(m1, m2, amount) {
return m1.map((a, ndx) => {
const b = m2[ndx];
return a + (b - a) * amount;
});
}
canvas { width: 100px; margin: 5px }
<script src="https://cdn.rawgit.com/phoboslab/WebGLImageFilter/e0eee0cd/webgl-image-filter.js"></script>
Which seems to work?
const identity = [
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0,
];
const sepia = [
0.393, 0.7689999, 0.18899999, 0, 0,
0.349, 0.6859999, 0.16799999, 0, 0,
0.272, 0.5339999, 0.13099999, 0, 0,
0,0,0,1,0,
];
const image = new Image();
image.crossOrigin = "anonymous";
image.src = "https://cdn.rawgit.com/una/CSSgram/6f21810a/site/img/atx.jpg";
image.onload = function() {
try {
var filter = new WebGLImageFilter();
}
catch( err ) {
console.log(err)
}
filter.addFilter('brightness',-0.05); // 1.05);
// filter.addFilter('sepia',0.2);
filter.addFilter('colorMatrix', mix(identity, sepia, 0.2));
filter.addFilter('contrast', -0.1); // 0.9);
filter.addFilter('saturation', -0.1); //0.9);
var filteredImage = filter.apply(image);
document.body.appendChild(image);
document.body.appendChild(filteredImage);
}
function mix(m1, m2, amount) {
return m1.map((a, ndx) => {
const b = m2[ndx];
return a + (b - a) * amount;
});
}
img, canvas {
width: 300px;
margin: 5px;
}
<script src="https://cdn.rawgit.com/phoboslab/WebGLImageFilter/e0eee0cd/webgl-image-filter.js"></script>
I have an image like this:
and like to find a specific color in the image (#ffa400 / #ffffff) and get their percentage occurrence value (orange maybe: 75%, white: 25%)
NOTE: Keep in mind that I don't like to get the average color (in this case: orange) like Color Thief does, but I'D like to find a SPECIFIC color.
Below is a snippet, so this is all that I got so far, but the code doesn't seem working & my idea also doesn't seem like an efficient & fast way.
function extract_colors(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 getColors(c);
}
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];
if (a < (255 / 2))
continue;
col = rgbToHex(r, g, b);
if (col == 'ffa400') { // find color #ffa400
if (!colors[col])
colors[col] = 0;
colors[col] + +;
}
}
return colors;
}
function rgbToHex(r, g, b) {
return ((r << 16) | (g << 8) | b).toString(16);
}
var img = document.getElementById('img');
out.innerHTML = extract_colors(img)
<img id='img' width=100 src='https://i.stack.imgur.com/EPDlQ.png'>
<p id='out'>...</p>
EDIT 1.0
I'm trying to use a code like this, but it doesn't seems to work either.
var orangeMatches=0, whiteMatches=0
Jimp.read("https://i.stack.imgur.com/EPDlQ.png").then(function (image) {
image.scan(0, 0, image.bitmap.width, image.bitmap.height, function (x, y, idx) {
var red = this.bitmap.data[ idx + 0 ];
var green = this.bitmap.data[ idx + 1 ];
var blue = this.bitmap.data[ idx + 2 ];
var alpha = this.bitmap.data[ idx + 3 ];
if (red == 255 && green == 164 && blue == 0 && alpha == 1){
orangeMatches++;
}
if (red == 255 && green == 255 && blue == 255 && alpha == 1){
whiteMatches++;
}
});
console.log(orangeMatches, whiteMatches)
});
Edit 2.0
I'd like to have some kind of tolerant value (10%) so that there is not every color to be counted, but the colors (e.g #ffa400 #ffa410) that are matching together are counted together.
A few notes
The main color in your picture is actually ffa500, not ffa400. There are in fact no occurances of ffa400 at all.
Also keep in mind that this is an anti-aliased bitmap graphic. Visually we see only two colors, but to smoothen the transition from one color to another a set of "in-between-colors" are generated. In other words, the sum of ffffff and ffa500 will not be exactly 100%. (This is why the output from my function below will display a lot more colors than the two in question)
Solution 1
The basic idea of this solution is to get every color in the picture, store these in one common object where the occurrence of each color is counted. When this object is returned, we can later count the occurrence of some specific color from the output.
This will return something like:
{
ffa500: 7802,
ffa501: 4,
ffa502: 2,
...,
ffffff: 1919,
total: 10000
}
Now you can access the occurences of a given color this way:
var imageData = extract_colors(img),
white = imageData.ffffff,
total = imageData.total;
And to display how many percent of the image a given color covers, do something like this (toFixed() is simply to limit the value to two decimals):
whitePct = (white / total * 100).toFixed(2);
Working sample
function getColors(ctx) {
// Get the canvas data
var pixels = ctx.getImageData(0, 0, ctx.width, ctx.height),
data = pixels.data,
// Set up our output object to collect color data
output = {};
// For each color we encounter, check the
// output object. If the color already exists
// there, simply increase its counted value.
// If it does not, create a new key.
for (var i = 0; i < data.length; i+=4) {
var r = data[i],
g = data[i + 1],
b = data[i + 2],
col = rgbToHex(r, g, b);
if( output[col] )
output[col]++
else
output[col] = 1
}
// Count total
var total = 0;
for(var key in output) {
total = total + parseInt(output[key])
}
output.total = total;
// Return the color data as an object
return output;
}
// Our elements
var img = document.getElementById('img'),
out = document.getElementById('out');
// Retrieve the image data
var imageData = extract_colors(img),
// Count our given colors
white = imageData.ffffff,
orange = imageData.ffa500,
total = imageData.total,
// Calculate percentage value
whitePct = (white / total * 100).toFixed(2),
orangePct = (orange / total * 100).toFixed(2);
out.innerHTML = `White: ${whitePct}%<br/>Orange: ${orangePct}%`;
// See the console for all colors identified
console.log(extract_colors(img))
// ----- These functions are left untouched ----- \\
function extract_colors(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 getColors(c);
}
function rgbToHex(r, g, b) {
return ((r << 16) | (g << 8) | b).toString(16);
}
p, img {
margin: 10px 5px;
float: left
}
<!-- Image converted to base64 to keep it locally availible -->
<img id='img' width=100 src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAZdEVYdFNvZnR3YXJlAEFkb2JlIEltYWdlUmVhZHlxyWU8AAADImlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo4QUUwNzk4N0I2QjIxMUUzQUQzMkIzMTA4REQ0Nzc1MiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo4QUUwNzk4OEI2QjIxMUUzQUQzMkIzMTA4REQ0Nzc1MiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkI1NDU5MjAyQjZCMTExRTNBRDMyQjMxMDhERDQ3NzUyIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjhBRTA3OTg2QjZCMjExRTNBRDMyQjMxMDhERDQ3NzUyIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwv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'>
<p id='out'>...</p>
Solution 2
(Consider this solution a draft. It might need some tweaking if to be used for a live website, but at least it should work!)
The idea here is to call extract_colors() with the colors we want to map as arguments. A common object is built to hold the output, and this is populated with one object for each color. The color objects consists of a counter and an rgb value. Using the isNeighborColor() function, the rgb value is used to match not only the specific color but also any color close to it. Adjust the tolerance level as you want. (Ideally this would be another argument I guess).
Call the function specifying the image and what colors to count the occurrence of:
var imageData = extract_colors(img, 'ffffff', 'ffa500'),
This will output an object for each color:
{
"ffffff": {
"counter": 1979,
"rgb": {
"r": 255,
"g": 255,
"b": 255
}
},
"ffa500": {
"counter": 7837,
"rgb": {
"r": 255,
"g": 165,
"b": 0
}
},
"total": 9816
}
What we want is to access the counter values:
var white = imageData.ffffff.counter;
Working sample
// Added a rest parameter for unlimited color input
function extract_colors(img, ...colors) {
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 getColors(c, ...colors);
}
// Unchanged
function rgbToHex(r, g, b) {
return ((r << 16) | (g << 8) | b).toString(16);
}
// From: https://stackoverflow.com/a/5624139/2311559
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
// From: https://stackoverflow.com/a/11506531/2311559
// Slightly modified
function isNeighborColor(color1, color2, tolerance) {
tolerance = tolerance || 32;
return Math.abs(color1.r - color2.r) <= tolerance
&& Math.abs(color1.g - color2.g) <= tolerance
&& Math.abs(color1.b - color2.b) <= tolerance;
}
function getColors(ctx, ...colors) {
var pixels = ctx.getImageData(0, 0, ctx.width, ctx.height),
data = pixels.data,
output = {};
// Build an 'output' object. This will hold one object for
// each color we want to check. The color objects will
// each consist of a counter and an rgb value. The counter
// is used to count the occurrence of each color. The rgb
// value is used to match colors with a certain tolerance
// using the 'isNeighborColor()' function above.
for (var i = 0; i < colors.length; i++) {
output[colors[i]] = {
counter: 0,
rgb: hexToRgb(colors[i])
};
}
// For each pixel, match its color against the colors in our
// 'output' object. Using the 'isNeighborColor()' function
// we will also match colors close to our input, given the
// tolerance defined.
for (var i = 0; i < data.length; i+=4) {
var r = data[i],
g = data[i + 1],
b = data[i + 2],
colobj = {r, g, b},
col = rgbToHex(r, g, b);
Object.keys(output).map(function(objectKey, index) {
var rgb = output[objectKey].rgb,
count = output[objectKey].counter;
if(isNeighborColor(rgb, colobj)) {
output[objectKey].counter = count+1;
}
});
}
// Count total
var total = 0;
for(var key in output) {
total = total + parseInt(output[key].counter)
}
output.total = total;
// Return the color data as an object
return output;
}
// Our elements
var img = document.getElementById('img'),
out = document.getElementById('out');
// Retrieve the image data
var imageData = extract_colors(img,'ffffff','ffa500'),
// Count our given colors
white = imageData.ffffff.counter,
orange = imageData.ffa500.counter,
total = imageData.total,
// Calculate percentage value
whitePct = (white / total * 100).toFixed(2),
orangePct = (orange / total * 100).toFixed(2);
out.innerHTML = `White: ${whitePct}%<br/>Orange: ${orangePct}%`;
// Optional console output
console.log(extract_colors(img,'ffffff','ffa500'));
p, img {
margin: 10px 5px;
float: left
}
<!-- Image converted to base64 to keep it locally availible -->
<img id='img' width=100 src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAZdEVYdFNvZnR3YXJlAEFkb2JlIEltYWdlUmVhZHlxyWU8AAADImlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo4QUUwNzk4N0I2QjIxMUUzQUQzMkIzMTA4REQ0Nzc1MiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo4QUUwNzk4OEI2QjIxMUUzQUQzMkIzMTA4REQ0Nzc1MiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkI1NDU5MjAyQjZCMTExRTNBRDMyQjMxMDhERDQ3NzUyIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjhBRTA3OTg2QjZCMjExRTNBRDMyQjMxMDhERDQ3NzUyIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwv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'>
<p id='out'>...</p>
Is there a algorithm to get the line strokes of a image (ignore curves, circles, etc., everything will be treated as lines, but still similiar to vectors), from their pixels? Then get a result of them, like a Array?
This is how it'd basically work to read
In this way, each row of pixel would be read as 1 horizontal line and I'd like to handle vertical lines also; but if there's a round fat line that takes more than 1 row
it'll be considered one line. Its line width is the same height of pixels it has.
For instance, let's suppose we've a array containing rows of pixels in the (red, green, blue, alpha) format (JavaScript):
/* formatted ImageData().data */
[
new Uint8Array([
/* first pixel */
255, 0, 0, 255,
/* second pixel */
255, 0, 0, 255
]),
new Uint8Array([
/* first pixel */
0, 0, 0, 0,
/* second pixel */
0, 0, 0, 0
])
]
This would be a 2x2px image data, with a straight horizontal red line. So, from this array, I want to get a array containing data of lines, like:
[
// x, y: start point
// tx, ty: end point
// w: line width
// the straight horizontal red line of 1 pixel
{ x: 0, y: 0, tx: 2, ty: 0, w: 1, rgba: [255, 0, 0, 255] }
]
Note: I'd like to handle anti-aliasing.
This is my function to read pixels in the above format:
var getImagePixels = function(img){
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
var imgData = ctx.getImageData(0, 0, img.width, img.height).data;
var nImgData = [];
var offWidth = img.width * 4;
var dataRow = (nImgData[0] = new Uint8Array(offWidth));
for (var b = 0, i = 0; b++ < img.height;) {
nImgData[b] = new Uint8Array(offWidth);
for (var arrI = 0, len = i + offWidth; i < len; i += 4, arrI += 4) {
nImgData[b][arrI] = imgData[i];
nImgData[b][arrI + 1] = imgData[i + 1];
nImgData[b][arrI + 2] = imgData[i + 2];
nImgData[b][arrI + 3] = imgData[i + 3];
}
}
return nImgData;
};
You can find all lines using Hough transform. It will find only lines, no curves or circles. You may need to run edge detection before finding lines. Here is example:
Here you can find opencv example of implementation.
I had a similar image processing question once, you can read it here. But you can basically take the same idea for anything you want to do with an image.
The basic data can be seen as follows:
var img = new Image,
w = canvas.width,
h = canvas.height,
ctx = canvas.getContext('2d');
img.onload = imgprocess;
img.src = 'some.png';
function imgprocess() {
ctx.drawImage(this, 0, 0, w, h);
var idata = ctx.getImageData(0, 0, w, h),
buffer = idata.data,
buffer32 = new Uint32Array(buffer.buffer),
x, y,
x1 = w, y1 = h, x2 = 0, y2 = 0;
//You now have properties of the image from the canvas data. You will need to write your own loops to detect which pixels etc... See the example in the link for some ideas.
}
UPDATE:
Working example of finding color data;
var canvas = document.getElementById('canvas');
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var ctx = canvas.getContext('2d');
var imageData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
var buf = new ArrayBuffer(imageData.data.length);
var buf8 = new Uint8ClampedArray(buf);
var data = new Uint32Array(buf);
for (var y = 0; y < canvasHeight; ++y) {
for (var x = 0; x < canvasWidth; ++x) {
var value = x * y & 0xff;
data[y * canvasWidth + x] =
(255 << 24) | // alpha
(value << 16) | // blue
(value << 8) | // green
value; // red
}
}
More examples can be seen here
The author above outlines pixel and line data:
The ImageData.data property referenced by the variable data is a one-dimensional array of integers, where each element is in the range 0..255. ImageData.data is arranged in a repeating sequence so that each element refers to an individual channel. That repeating sequence is as follows:
data[0] = red channel of first pixel on first row
data[1] = green channel of first pixel on first row
data[2] = blue channel of first pixel on first row
data[3] = alpha channel of first pixel on first row
data[4] = red channel of second pixel on first row
data[5] = green channel of second pixel on first row
data[6] = blue channel of second pixel on first row
data[7] = alpha channel of second pixel on first row
data[8] = red channel of third pixel on first row
data[9] = green channel of third pixel on first row
data[10] = blue channel of third pixel on first row
data[11] = alpha channel of third pixel on first row