Get a pixel from HTML Canvas? - javascript

Is it possible to query a HTML Canvas object to get the color at a specific location?

There's a section about pixel manipulation in the W3C documentation.
Here's an example on how to invert an image:
var context = document.getElementById('myCanvas').getContext('2d');
// Get the CanvasPixelArray from the given coordinates and dimensions.
var imgd = context.getImageData(x, y, width, height);
var pix = imgd.data;
// Loop over each pixel and invert the color.
for (var i = 0, n = pix.length; i < n; i += 4) {
pix[i ] = 255 - pix[i ]; // red
pix[i+1] = 255 - pix[i+1]; // green
pix[i+2] = 255 - pix[i+2]; // blue
// i+3 is alpha (the fourth element)
}
// Draw the ImageData at the given (x,y) coordinates.
context.putImageData(imgd, x, y);

Try the getImageData method:
var data = context.getImageData(x, y, 1, 1).data;
var rgb = [ data[0], data[1], data[2] ];

Yes sure, provided you have its context. (See how to get canvas context here.)
var imgData = context.getImageData(0,0,canvas.width,canvas.height)
// { data: [r,g,b,a,r,g,b,a,r,g,..], ... }
function getPixel(imgData, index) {
var i = index*4, d = imgData.data
return [d[i],d[i+1],d[i+2],d[i+3]] // Returns array [R,G,B,A]
}
// AND/OR
function getPixelXY(imgData, x, y) {
return getPixel(imgData, y*imgData.width+x)
}
PS: If you plan to mutate the data and draw them back on the canvas, you can use subarray
var
idt = imgData, // See previous code snippet
a = getPixel(idt, 188411), // Array(4) [0, 251, 0, 255]
b = idt.data.subarray(188411*4, 188411*4 + 4) // Uint8ClampedArray(4) [0, 251, 0, 255]
a[0] = 255 // Does nothing
getPixel(idt, 188411) // Array(4) [0, 251, 0, 255]
b[0] = 255 // Mutates the original imgData.data
getPixel(idt, 188411) // Array(4) [255, 251, 0, 255]
// Or use it in the function
function getPixel(imgData, index) {
var i = index*4, d = imgData.data
return imgData.data.subarray(i, i+4) // Returns subarray [R,G,B,A]
}
You can experiment with this on http://qry.me/xyscope/, the code for this is in the source, just copy/paste it in the console.

function GetPixel(context, x, y)
{
var p = context.getImageData(x, y, 1, 1).data;
var hex = "#" + ("000000" + rgbToHex(p[0], p[1], p[2])).slice(-6);
return hex;
}
function rgbToHex(r, g, b) {
if (r > 255 || g > 255 || b > 255)
throw "Invalid color component";
return ((r << 16) | (g << 8) | b).toString(16);
}

Yup, check out getImageData(). Here's an example of breaking CAPTCHA with JavaScript using canvas:
OCR and Neural Nets in JavaScript

Note that getImageData returns a snapshot. Implications are:
Changes will not take effect until subsequent putImageData
getImageData and putImageData calls are relatively slow

//Get pixel data
var imageData = context.getImageData(x, y, width, height);
//Color at (x,y) position
var color = [];
color['red'] = imageData.data[((y*(imageData.width*4)) + (x*4)) + 0];
color['green'] = imageData.data[((y*(imageData.width*4)) + (x*4)) + 1];
color['blue'] = imageData.data[((y*(imageData.width*4)) + (x*4)) + 2];
color['alpha'] = imageData.data[((y*(imageData.width*4)) + (x*4)) + 3];

You can use i << 2.
const data = context.getImageData(x, y, width, height).data;
const pixels = [];
for (let i = 0, dx = 0; dx < data.length; i++, dx = i << 2) {
pixels.push({
r: data[dx ],
g: data[dx+1],
b: data[dx+2],
a: data[dx+3]
});
}

Fast and handy
Use following class which implement fast method described in this article and contains all you need: readPixel, putPixel, get width/height. Class update canvas after calling refresh() method. Example solve simple case of 2d wave equation
class Screen{
constructor(canvasSelector) {
this.canvas = document.querySelector(canvasSelector);
this.width = this.canvas.width;
this.height = this.canvas.height;
this.ctx = this.canvas.getContext('2d');
this.imageData = this.ctx.getImageData(0, 0, this.width, this.height);
this.buf = new ArrayBuffer(this.imageData.data.length);
this.buf8 = new Uint8ClampedArray(this.buf);
this.data = new Uint32Array(this.buf);
}
// r,g,b,a - red, gren, blue, alpha components in range 0-255
putPixel(x,y,r,g,b,a=255) {
this.data[y * this.width + x] = (a << 24) | (b << 16) | (g << 8) | r;
}
readPixel(x,y) {
let p= this.data[y * this.width + x]
return [p&0xff, p>>8&0xff, p>>16&0xff, p>>>24];
}
refresh() {
this.imageData.data.set(this.buf8);
this.ctx.putImageData(this.imageData, 0, 0);
}
}
// --------
// TEST
// --------
let s=new Screen('#canvas');
function draw() {
for (var y = 1; y < s.height-1; ++y) {
for (var x = 1; x < s.width-1; ++x) {
let a = [[1,0],[-1,0],[0,1],[0,-1]].reduce((a,[xp,yp])=>
a+= s.readPixel(x+xp,y+yp)[0]
,0);
let v=a/2-tmp[x][y];
tmp[x][y]=v<0 ? 0:v;
}
}
for (var y = 1; y < s.height-1; ++y) {
for (var x = 1; x < s.width-1; ++x) {
let v=tmp[x][y];
tmp[x][y]= s.readPixel(x,y)[0];
s.putPixel(x,y, v,v,v);
}
}
s.refresh();
window.requestAnimationFrame(draw)
}
// temporary 2d buffer ()for solving wave equation)
let tmp = [...Array(s.width)].map(x => Array(s.height).fill(0));
function move(e) { s.putPixel(e.x-10, e.y-10, 255,255,255);}
draw();
<canvas id="canvas" height="150" width="512" onmousemove="move(event)"></canvas>
<div>Move mouse on black box</div>

If you want to extract a particular color of pixel by passing the coordinates of pixel into the function, this will come in handy:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
function detectColor(x, y){
data=ctx.getImageData(x, y, 1, 1).data;
col={
r:data[0],
g:data[1],
b:data[2]
};
return col;
}
x, y is the coordinate you want to filter out color.
var color = detectColor(x, y)
The color is the object, you will get the RGB value by color.r, color.g, color.b.

Related

How can I fill color between 4 lines when it connect in canvas?

I am trying to fill color between lines when it connects in ionic. I want to fill color between line when four-line touch each other. For that, I created a canvas demo using a touch event. Please help to solve my issue.
We have 4 lines in the canvas box and we will drag them and connect each line and give a shape line box. that means our line is connected now fill color between line so the box is filled up with color.
html file:
<canvas #canvasDraw width="300" height="300" (touchstart)="handleTouchStart($event)"
(touchmove)="handleTouchmove($event)"
(touchend)="handleTouchEnd($event)">
You need a browser that supports HTML5!
</canvas>
ts file:
import { Component, ElementRef, ViewChild } from '#angular/core';
#Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage {
#ViewChild('canvasDraw', { static: false }) canvas: ElementRef;
canvasElement: any;
lines: any[];
isDown: boolean = false;
startX: number;
startY: number;
nearest: any;
offsetX: any;
offsetY: any;
constructor() {
}
ngAfterViewInit() {
this.canvasElement = this.canvas.nativeElement;
this.lines = [];
this.lines.push({ x0: 75, y0: 25, x1: 125, y1: 25 });
this.lines.push({ x0: 75, y0: 100, x1: 125, y1: 100 });
this.lines.push({ x0: 50, y0: 35, x1: 50, y1: 85 });
this.lines.push({ x0: 150, y0: 35, x1: 150, y1: 85 });
this.draw();
//this.reOffset();
requestAnimationFrame(() => {
this.reOffset()
})
}
reOffset() {
let BB = this.canvasElement.getBoundingClientRect();
this.offsetX = BB.left;
this.offsetY = BB.top;
}
// select the this.nearest line to the mouse
closestLine(mx, my) {
let dist = 100000000;
let index, pt;
for (let i = 0; i < this.lines.length; i++) {
//
let xy = this.closestXY(this.lines[i], mx, my);
//
let dx = mx - xy.x;
let dy = my - xy.y;
let thisDist = dx * dx + dy * dy;
if (thisDist < dist) {
dist = thisDist;
pt = xy;
index = i;
}
}
let line = this.lines[index];
return ({ pt: pt, line: line, originalLine: { x0: line.x0, y0: line.y0, x1: line.x1, y1: line.y1 } });
}
// linear interpolation -- needed in setClosestLine()
lerp(a, b, x) {
return (a + x * (b - a));
}
// find closest XY on line to mouse XY
closestXY(line, mx, my) {
let x0 = line.x0;
let y0 = line.y0;
let x1 = line.x1;
let y1 = line.y1;
let dx = x1 - x0;
let dy = y1 - y0;
let t = ((mx - x0) * dx + (my - y0) * dy) / (dx * dx + dy * dy);
t = Math.max(0, Math.min(1, t));
let x = this.lerp(x0, x1, t);
let y = this.lerp(y0, y1, t);
return ({ x: x, y: y });
}
// draw the scene
draw() {
let ctx = this.canvasElement.getContext('2d');
let cw = this.canvasElement.width;
let ch = this.canvasElement.height;
ctx.clearRect(0, 0, cw, ch);
// draw all lines at their current positions
for (let i = 0; i < this.lines.length; i++) {
this.drawLine(this.lines[i], 'black');
}
// draw markers if a line is being dragged
if (this.nearest) {
// point on line this.nearest to mouse
ctx.beginPath();
ctx.arc(this.nearest.pt.x, this.nearest.pt.y, 5, 0, Math.PI * 2);
ctx.strokeStyle = 'red';
ctx.stroke();
// marker for original line before dragging
this.drawLine(this.nearest.originalLine, 'red');
// hightlight the line as its dragged
this.drawLine(this.nearest.line, 'red');
}
}
drawLine(line, color) {
let ctx = this.canvasElement.getContext('2d');
ctx.beginPath();
ctx.moveTo(line.x0, line.y0);
ctx.lineTo(line.x1, line.y1);
ctx.strokeStyle = color;
ctx.stroke();
}
handleTouchStart(e) {
// tell the browser we're handling this event
let tch = e.touches[0];
// tch.preventDefault();
// tch.stopPropagation();
// mouse position
this.startX = tch.clientX - this.offsetX;
this.startY = tch.clientY - this.offsetY;
// find this.nearest line to mouse
this.nearest = this.closestLine(this.startX, this.startY);
this.draw();
// set dragging flag
this.isDown = true;
}
handleTouchEnd(e) {
// tell the browser we're handling this event
let tch = e.touches[0];
// tch.preventDefault();
// tch.stopPropagation();
// clear dragging flag
this.isDown = false;
this.nearest = null;
this.draw();
}
handleTouchmove(e) {
if (!this.isDown) { return; }
// tell the browser we're handling this event
let tch = e.touches[0];
// tch.preventDefault();
// tch.stopPropagation();
// mouse position
const mouseX = tch.clientX - this.offsetX;
const mouseY = tch.clientY - this.offsetY;
// calc how far mouse has moved since last mousemove event
let dx = mouseX - this.startX;
let dy = mouseY - this.startY;
this.startX = mouseX;
this.startY = mouseY;
// change this.nearest line vertices by distance moved
let line = this.nearest.line;
line.x0 += dx;
line.y0 += dy;
line.x1 += dx;
line.y1 += dy;
// redraw
this.draw();
let ctx = this.canvasElement.getContext('2d');
ctx.beginPath();
ctx.rect(line.x0, line.y0, line.x1, line.y1);
ctx.fillStyle = "red";
ctx.fill();
}
}
How to fill color when four line touch or connect?
How to get a pixel's color
Basically, when you do your touch event, pixels are changing color. You can find out which pixel(s) are/were affected from the event. Having the pixel(s) as input you can find out what color a pixel has: https://jsfiddle.net/ourcodeworld/8swevoxo/
var canvas = document.getElementById("canvas");
function getPosition(obj) {
var curleft = 0, curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
return { x: curleft, y: curtop };
}
return undefined;
}
function rgbToHex(r, g, b) {
if (r > 255 || g > 255 || b > 255)
throw "Invalid color component";
return ((r << 16) | (g << 8) | b).toString(16);
}
function drawImageFromWebUrl(sourceurl){
var img = new Image();
img.addEventListener("load", function () {
// The image can be drawn from any source
canvas.getContext("2d").drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height);
});
img.setAttribute("src", sourceurl);
}
// Draw a base64 image because this is a fiddle, and if we try with an image from URL we'll get tainted canvas error
// Read more about here : http://ourcodeworld.com/articles/read/182/the-canvas-has-been-tainted-by-cross-origin-data-and-tainted-canvases-may-not-be-exported
drawImageFromWebUrl("data:image/gif;base64,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");
canvas.addEventListener("mousemove",function(e){
var pos = getPosition(this);
var x = e.pageX - pos.x;
var y = e.pageY - pos.y;
var coord = "x=" + x + ", y=" + y;
var c = this.getContext('2d');
var p = c.getImageData(x, y, 1, 1).data;
// If transparency on the image
if((p[0] == 0) && (p[1] == 0) && (p[2] == 0) && (p[3] == 0)){
coord += " (Transparent color detected, cannot be converted to HEX)";
}
var hex = "#" + ("000000" + rgbToHex(p[0], p[1], p[2])).slice(-6);
document.getElementById("status").innerHTML = coord;
document.getElementById("color").style.backgroundColor = hex;
},false);
<canvas id="canvas" width="150" height="150"></canvas>
<div id="status"></div><br>
<div id="color" style="width:30px;height:30px;"></div>
<p>
Move the mouse over the BUS !
</p>
This code was not written by me.
Understanding the problem
Now that we know how to get the color pixel by pixel, we need to convert our question to something that is equivalent, but it's easier to compute. I would define the problem as follows:
Traverse pixels starting from a given point in a continuous manner in
order to find out whether a cycle can be formed, which contains pixels
of a certain (default) color inside the boundary, which should change
their color to some fill color.
How to solve it
Start a cycle of pixel traversing starting from a given point and always changing coordinate values in each step to a point neighboring the current point or some point visited earlier in the loop
Always store the coordinates of the currently visited point so if you would visit the same point, you just ignore it on the second time
Use a data structure, like a stack to store all neighbors to visit, but not yet visited, put in the stack all neighbors of each point you visit (unless the point was already visited)
If you ever arrive back to the starting point from a point which was not already visited, then register that this is a cycle
Always keep track of boundary points
When you found out that it's a cycle and you know where it is located, traverse each point in the region and if it has a default color, check whether there is a continuous line by which you could "leave" the cycle by visiting only neighbors of default color, pretty similarly as you have checked whether it is a cycle. If not, then paint the pixel (and all its neighbors of similar color) to the color you need
I wrote the following example using web technologies as im not familiar with ionic.
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
// compute signed triangle area of triangle abc. Negative if triangle is cw.
const area = (a, b, c) => {
return (a[0] - c[0]) * (b[1] - c[1]) - (a[1] - c[1]) * (b[0] - c[0]);
};
// compute intersections of line segments.
const intersect = (s0, s1) => {
let result = null;
const a1 = area(s0.p0, s0.p1, s1.p1);
const a2 = area(s0.p0, s0.p1, s1.p0);
if (a1 * a2 < 0) {
const a3 = area(s1.p0, s1.p1, s0.p0);
const a4 = a3 + a2 - a1;
if (a3 * a4 < 0) {
const t = a3 / (a3 - a4);
result = [
s0.p0[0] + t * (s0.p1[0] - s0.p0[0]),
s0.p0[1] + t * (s0.p1[1] - s0.p0[1])
];
}
}
return result;
};
const dot = (a, b) => a[0] * b[0] + a[1] * b[1];
const sub = (a, b) => [a[0] - b[0], a[1] - b[1]];
const clamp = (x, min, max) => Math.min(max, Math.max(x, min));
const min = (points) => {
return points.reduce((r, p) => {
return [
Math.min(p[0], r[0]),
Math.min(p[1], r[1])
];
}, [Infinity, Infinity]);
};
const max = (points) => {
return points.reduce((r, p) => {
return [
Math.max(p[0], r[0]),
Math.max(p[1], r[1])
];
}, [-Infinity, -Infinity]);
};
const state = {
lines: [{
p0: [75, 25],
p1: [125, 25]
},
{
p0: [75, 100],
p1: [125, 100]
},
{
p0: [50, 35],
p1: [50, 85]
},
{
p0: [150, 35],
p1: [150, 85]
}
],
dragging: false,
target: null,
m: null
};
const draw = (state) => {
context.clearRect(0, 0, width, height);
const intersections = [];
for (let i = 0; i < state.lines.length; i++) {
const lines = state.lines;
const l0 = lines[i];
context.strokeStyle = "black";
for (let j = 0; j < lines.length; j++) {
if (j !== i) {
const intersection = intersect(l0, lines[j]);
if (intersection != null) {
intersections.push(intersection);
context.strokeStyle = "blue";
}
}
}
context.beginPath();
context.moveTo(l0.p0[0], l0.p0[1]);
context.lineTo(l0.p1[0], l0.p1[1]);
context.stroke();
}
if (intersections.length == 8) {
const p = min(intersections);
const q = max(intersections);
context.fillStyle = "red";
context.fillRect(p[0], p[1], q[0] - p[0], q[1] - p[1]);
}
};
const run = () => {
requestAnimationFrame(() => {
draw(state);
run();
});
};
canvas.addEventListener('mousedown', (e) => {
const rect = e.target.getBoundingClientRect();
const m = [e.x - rect.x, e.y - rect.y];
const lines = state.lines
.map((line) => {
const ab = sub(line.p1, line.p0);
const am = sub(m, line.p0);
const bm = sub(m, line.p1);
const e = dot(am, ab);
if (e <= 0) {
return dot(am, am);
}
const f = dot(ab, ab);
if (e >= f) {
return dot(bm, bm);
}
return dot(am, am) - e * e / f;
})
.map((d, i) => [d, state.lines[i]])
.sort((a, b) => a[0] - b[0]);
const first = lines[0];
if (first[0] < 50) {
state.dragging = true;
state.target = first[1];
state.m = m;
}
});
canvas.addEventListener('mouseup', () => {
state.dragging = false;
state.target = null;
state.m = null;
});
canvas.addEventListener('mousemove', (e) => {
if (state.dragging) {
const rect = e.target.getBoundingClientRect();
const m = [e.x - rect.x, e.y - rect.y];
const dx = m[0] - state.m[0];
const dy = m[1] - state.m[1];
state.target.p0[0] = dx + state.target.p0[0];
state.target.p0[1] = dy + state.target.p0[1];
state.target.p1[0] = dx + state.target.p1[0];
state.target.p1[1] = dy + state.target.p1[1];
state.m = m;
}
}, {
passive: true
});
run();
<canvas width="600" height="800"><canvas>
Solution
The real meat of the solution is the intersect function. What this does it it takes two linesegments and calculates the point that they intersect. This is really the only thing that you were missing. The intersect algorithm is a bit too long to discuss here but you can take a look at the details here. After we do a pretty inefficient O(n^2) intersection query between every single line segment. We then can figure out if we have have enough intersections for our rectangle. I compute both A -> B and B -> A intersections discretely with out any regard of culling the intersection candidate since i'm dealing with 4 line segments. What this means is we need 8 intersections to close our rectangle. Once we know we have a closed polygon we can draw a rectangle using the min(...)/max(...) of all x/y components of all intersections. This gives us two points P,Q that we can then use to fill in a rect.
If you are into this kind of thing I highly recommend the book Real Time Collision Detection by Christer Ericson or similar.

Successives canvas manipulations optimization

Hi I making an app where I have to manipulate some png: extract some color, create some outline etc...
Each time I repeat the same process:
Wait for image load in DOM
Create a new canvas with size
Add context2D
DrawImage
GetImageData
Do some stuff with a loop through all data pixel
Put the new stuff (putImageData) in a empty pixel data array (create with createImageData)
Link it to a new canvas
Create anew image from this canvas
repeat
For example:
var imgColor = new Image();
imgColor.src = this[`canvasColor${color}`].toDataURL("image/png");
// wait for load of this new color image
imgColor.onload = () => {
this[`canvasColorAndOutline${color}`].width = width;
this[`canvasColorAndOutline${color}`].height = height;
var outlineAndColorCtx = this[`canvasColorAndOutline${color}`].getContext("2d");
var dArr = [-1, -1, 0, -1, 1, -1, -1, 0, 1, 0, -1, 1, 0, 1, 1, 1], // offset array
s = this.state.outlineThickness, // thickness
i = 0, // iterator
x = 0, // final position
y = 0;
// draw images at offsets from the array scaled by s
for (; i < dArr.length; i += 2) {
outlineAndColorCtx.drawImage(imgColor, x + dArr[i] * s, y + dArr[i + 1] * s);
}
// fill with color
outlineAndColorCtx.globalCompositeOperation = "source-in";
outlineAndColorCtx.fillStyle = "YELLOW";
outlineAndColorCtx.fillRect(0, 0, width, height);
// draw original image in normal mode
outlineAndColorCtx.globalCompositeOperation = "source-over";
outlineAndColorCtx.drawImage(imgColor, x, y);
///////////////
// THIRD STEP : remove the white to keep the outline
//////////////
// create a new image with this color context to work on
var imgOutline = new Image();
imgOutline.src = this[`canvasColorAndOutline${color}`].toDataURL("image/png");
imgOutline.onload = () => {
var imageDataOutlineAndColor = outlineAndColorCtx.getImageData(0, 0, width, height)
this[`canvasOutline${color}`].width = width;
this[`canvasOutline${color}`].height = height;
const outlineCtx = this[`canvasOutline${color}`].getContext("2d");
const imageDataOutline = outlineCtx.createImageData(width, height);
for (let i = 0; i < imageDataOutlineAndColor.data.length; i += 4) {
if (
(imageDataOutlineAndColor.data[i + 0] > 100) &&
(imageDataOutlineAndColor.data[i + 1] > 100) &&
(imageDataOutlineAndColor.data[i + 2] < 5) &&
(imageDataOutlineAndColor.data[i + 3] != 0)
) {
imageDataOutline.data[i + 0] = 255;
imageDataOutline.data[i + 1] = 255;
imageDataOutline.data[i + 2] = 0;
imageDataOutline.data[i + 3] = 255;
}
}
outlineCtx.putImageData(imageDataOutline, 0, 0);
}
}
My question is: Is there a way the shortcut step 7,8,9 to avoid the time of img.load? and directly use the context?
So I will use the same context all the time, it is just modify in each process step.
And more globally, is there a way to optimize it?

Find specific color passage / spectra in image

I have an image like the example below...
---> RESULT: #ffdf00 => something like 30%
... and I'd like to find the occurrence (percentage) of a specific color in the image (e.g. #ffdf00 - yellow), or even better I'd like to find the occurrence (percentage) of a specific color passage (e.g #ffdf00 ---too---> #dabf09) with simple javascript.
I've been already looking for javascript library's but all I found till' now is extracting the main color of the image like the plugins below:
getImageColor
VibrantJS
However I'm sure something like color thief can do the job, but I have no clue how to "SEARCH FOR SPECIFIC COLOR SPECTRA AND GET THE PERCENTAGE OCCURRENCE" (#ffdf00 ---too---> #dabf09), so I hope somebody can help me with my problem.
However, this is all what I got so far & now I've no clue how to continue...
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;
}
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 (!colors[col])
colors[col] = 0;
colors[col]++;
}
return colors;
}
function rgbToHex(r, g, b) {
return ((r << 16) | (g << 8) | b).toString(16);
}
function pad(hex) {
return ("000000" + hex).slice(-6);
}
var img=document.getElementById('img')
var colors = getColors(draw(img));
<img width=200 id='img' src='https://s.aolcdn.com/hss/storage/midas/b386937631a1f03665c1d57289070898/203417456/simpsons.jpg'>
Thanks a million in advance, jonas

How to divide image in tiles?

I have to achieve the following task:
divides the image into tiles, computes the average color of each tile,
fetches a tile from the server for that color, and composites the
results into a photomosaic of the original image.
What would be the best strategy? the first solution coming to my mind is using canvas.
A simple way to get pixel data and finding the means of tiles. The code will need more checks for images that do not have dimensions that can be divided by the number of tiles.
var image = new Image();
image.src = ??? // the URL if the image is not from your domain you will have to move it to your server first
// wait for image to load
image.onload = function(){
// create a canvas
var canvas = document.createElement("canvas");
//set its size to match the image
canvas.width = this.width;
canvas.height = this.height;
var ctx = canvas.getContext("2d"); // get the 2d interface
// draw the image on the canvas
ctx.drawImage(this,0,0);
// get the tile size
var tileSizeX = Math.floor(this.width / 10);
var tileSizeY = Math.floor(this.height / 10);
var x,y;
// array to hold tile colours
var tileColours = [];
// for each tile
for(y = 0; y < this.height; y += tileSizeY){
for(x = 0; x < this.width; x += tileSizeX){
// get the pixel data
var imgData = ctx.getImageData(x,y,tileSizeX,tileSizeY);
var r,g,b,ind;
var i = tileSizeY * tileSizeX; // get pixel count
ind = r = g = b = 0;
// for each pixel (rgba 8 bits each)
while(i > 0){
// sum the channels
r += imgData.data[ind++];
g += imgData.data[ind++];
b += imgData.data[ind++];
ind ++;
i --;
}
i = ind /4; // get the count again
// calculate channel means
r /= i;
g /= i;
b /= i;
//store the tile coords and colour
tileColours[tileColours.length] = {
rgb : [r,g,b],
x : x,
y : y,
}
}
// all done now fetch the images for the found tiles.
}
I created a solution for this (I am not getting the tile images from back end)
// first function call to create photomosaic
function photomosaic(image) {
// Dimensions of each tile
var tileWidth = TILE_WIDTH;
var tileHeight = TILE_HEIGHT;
//creating the canvas for photomosaic
var canvas = document.createElement('canvas');
var context = canvas.getContext("2d");
canvas.height = image.height;
canvas.width = image.width;
var imageData = context.getImageData(0, 0, image.width, image.height);
var pixels = imageData.data;
// Number of mosaic tiles
var numTileRows = image.width / tileWidth;
var numTileCols = image.height / tileHeight;
//canvas copy of image
var imageCanvas = document.createElement('canvas');
var imageCanvasContext = canvas.getContext('2d');
imageCanvas.height = image.height;
imageCanvas.width = image.width;
imageCanvasContext.drawImage(image, 0, 0);
//function for finding the average color
function averageColor(row, column) {
var blockSize = 1, // we can set how many pixels to skip
data, width, height,
i = -4,
length,
rgb = {
r: 0,
g: 0,
b: 0
},
count = 0;
try {
data = imageCanvasContext.getImageData(column * TILE_WIDTH, row * TILE_HEIGHT, TILE_HEIGHT, TILE_WIDTH);
} catch (e) {
alert('Not happening this time!');
return rgb;
}
length = data.data.length;
while ((i += blockSize * 4) < length) {
++count;
rgb.r += data.data[i];
rgb.g += data.data[i + 1];
rgb.b += data.data[i + 2];
}
// ~~ used to floor values
rgb.r = ~~(rgb.r / count);
rgb.g = ~~(rgb.g / count);
rgb.b = ~~(rgb.b / count);
return rgb;
}
// Loop through each tile
for (var r = 0; r < numTileRows; r++) {
for (var c = 0; c < numTileCols; c++) {
// Set the pixel values for each tile
var rgb = averageColor(r, c)
var red = rgb.r;
var green = rgb.g;
var blue = rgb.b;
// Loop through each tile pixel
for (var tr = 0; tr < tileHeight; tr++) {
for (var tc = 0; tc < tileWidth; tc++) {
// Calculate the true position of the tile pixel
var trueRow = (r * tileHeight) + tr;
var trueCol = (c * tileWidth) + tc;
// Calculate the position of the current pixel in the array
var pos = (trueRow * (imageData.width * 4)) + (trueCol * 4);
// Assign the colour to each pixel
pixels[pos + 0] = red;
pixels[pos + 1] = green;
pixels[pos + 2] = blue;
pixels[pos + 3] = 255;
};
};
};
};
// Draw image data to the canvas
context.putImageData(imageData, 0, 0);
return canvas;
}
function create() {
var image = document.getElementById('image');
var canvas = photomosaic(image);
document.getElementById("output").appendChild(canvas);
};
DEMO:https://jsfiddle.net/gurinderiitr/sx735L5n/
Try using the JIMP javascript library to read the pixel color and use invert, normalize or similar property for modifying the image.
Have a look on the jimp library
https://github.com/oliver-moran/jimp

Javascript - Most repeats pixels on background-image

How to find the most repetitive pixel in the background-image and find out the color?
Help!
You don't have access to the pixel data of a background image via JavaScript. What you will have to do is to create a new Image object and set the source to the background image URL. Afterwards, you will have to do these steps:
Create an in-memory canvas object
Draw the image on the canvas
Get the image data, iterate through all pixels and store the colors in an Object (key = color, value = amount of repitition)
Sort the array by the amount of repitition, then select the first value
Here, I created an example. This loads the JSconf logo and sets the body's background color to the most repetitive color.
// Create the image
var image = new Image();
image.crossOrigin = "Anonymous";
image.onload = function () {
var w = image.width, h = image.height;
// Initialize the in-memory canvas
var canvas = document.createElement("canvas");
canvas.width = w;
canvas.height = h;
// Get the drawing context
var context = canvas.getContext("2d");
// Draw the image to (0,0)
context.drawImage(image, 0, 0);
// Get the context's image data
var imageData = context.getImageData(0, 0, w, h).data;
// Iterate over the pixels
var colors = [];
for(var x = 0; x < w; x++) {
for(var y = 0; y < h; y++) {
// Every pixel has 4 color values: r, g, b, a
var index = ((y * w) + x) * 4;
// Extract the colors
var r = imageData[index];
var g = imageData[index + 1];
var b = imageData[index + 2];
// Turn rgb into hex so we can use it as a key
var hex = b | (g << 8) | (r << 16);
if(!colors[hex]) {
colors[hex] = 1;
} else {
colors[hex] ++;
}
}
}
// Transform into a two-dimensional array so we can better sort it
var _colors = [];
for(var color in colors) {
_colors.push([color, colors[color]]);
}
// Sort the array
_colors.sort(function (a, b) {
return b[1] - a[1];
});
var dominantColorHex = parseInt(_colors[0][0]).toString(16);
document.getElementsByTagName("body")[0].style.backgroundColor = "#" + dominantColorHex;
};
image.src = "http://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/JavaScript-logo.png/600px-JavaScript-logo.png";

Categories

Resources