Successives canvas manipulations optimization - javascript

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?

Related

A Javascript Canvas Loop to Check If Images Have a White Background

I want to loop over about 50 images in HTML, extract the src from each, check if the dominant color of the image background is white, then based on the results add some css styling (e.g padding).
So far I have this code however for some reason it's not working. The code works separately but when placed in a for loop it doesn't work. Usually, this loop either doesn't work at all or works only till some point then just feeds out a default result of "#FFFFFF" because the canvas itself is filled white.
I'm not sure why it's not working. I've been trying to fix it but to no avail.
DEMO HERE (please look through) : https://jsbin.com/tucegomemi/1/edit?html,js,console,output
JAVASCRIPT HERE :
var i;
var GlobalVariable;
for (i=0; i < document.querySelectorAll('img').length ; i++) {
let canvas = document.getElementById("canvas"),
canvasWidth = canvas.width,
canvasHeight = canvas.height,
c = canvas.getContext("2d"),
img = new Image();
img.crossOrigin="anonymous";
img.src = document.querySelectorAll('img')[i].src
// 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],
g = data[++index],
b = data[++index],
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;
}
}
GlobalVariable = most.colour;
console.log(i);
console.log(GlobalVariable);
if (GlobalVariable !== "#ffffff") {document.querySelectorAll('img')[i].style.padding = "50px" ;}
}
function rgbToHex(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
Wait for images to load
As the images are on the page you can wait for the page load event to fire. This will fire only when all the images have loaded (or fail to load). Do read the link as there are some caveats to using the load event.
Also as the images are on the page there is no need to create a copy of the image using new Image You can use the image directly from the page.
Also I assume that all images will load. If image do not load there will be problems
Looking at your code it is horrifically inefficient, thus the example is a complete rewrite with an attempt to run faster and chew less power.
Note: that the example uses a temp canvas that is in memory only. It does not need a canvas on the page.
Note: that it stop counting if a pixel has a count greater than half the number of pixels in the image.
addEventListener("load",() => { // wait for page (and images to load)
const toHex = val => (val & 0xFF).toString(16).padStart(2,"0"); // mask the to hex and pad with 0 if needed
const pixel2CSScolor = px => `#${toHex(px >> 16)}${toHex(px >> 8)}${toHex(px)}`;
const images = document.querySelectorAll('img');
const canvas = document.createElement("canvas"); // Only need one canvas
const ctx = canvas.getContext("2d"); // and only one context
for (const image of images) { // do each image in turn
const w = canvas.width = image.naturalWidth; // size to fit image
const h = canvas.height = image.naturalHeight;
ctx.fillStyle = "#FFF";
ctx.fillRect(0, 0, w, h);
ctx.drawImage(image, 0, 0);
const imgData = ctx.getImageData(0, 0, w, h);
const pixels = new Uint32Array(imgData.data.buffer); // get a pixel view of data (RGBA as one number)
const counts = {}; // a map of color counts
var idx = pixels.length, maxPx, maxCount = 0; // track the most frequent pixel count and type
while (idx-- > 0) {
const pixel = pixels[idx]; // get pixel
const count = counts[pixel] = counts[pixel] ? counts[pixel] + 1 : 1;
if (count > maxCount) {
maxCount = count;
maxPx = pixel;
if (count > pixels.length / 2) { break }
}
}
image._FOUND_DOMINATE_COLOR = pixel2CSScolor(maxPx);
}
});
Each image has a new property attached called _FOUND_DOMINATE_COLOR which hold a string with the colour as a CSS hex color
An even better way
As I am unsure of the image format and the content of the image the above example is the cover all solution.
If the images have large areas of similar color, or the image has a lot of noise you can use the GPU rendering to do most of the counting for you. This is done by drawing the image at progressively smaller scales. The drawImage function will average the pixel values as it does.
This means that when your code looks at the pixel data the is a lot less, Half the image size and there is 4 times less memory and CPU load, quarter the size and 16 times less work.
The next example reduces the image to 1/4 its natural size and then uses the averaged pixel values to find the color. Note that for the best results the images should be at least larger than 16 pixels in width and height
addEventListener("load",() => {
const toHex = val => (val & 0xFF).toString(16).padStart(2,"0");
const pixel2CSScolor = px => `#${toHex(px >> 16)}${toHex(px >> 8)}${toHex(px)}`;
const reduceImage = image => {
const w = canvas.width = image.naturalWidth;
const h = canvas.height = image.naturalHeight;
ctx.globalCompositeOperation = "source-over";
ctx.fillStyle = "#FFF";
ctx.fillRect(0, 0, w, h);
ctx.drawImage(image, 0, 0);
ctx.globalCompositeOperation = "copy";
ctx.drawImage(canvas, 0, 0, w / 2, h / 2);
ctx.drawImage(canvas, 0, 0, w / 2, h / 2, 0, 0, w / 4, h / 4);
return new Uint32Array(ctx.getImageData(0, 0, w / 4 | 0, h / 4 | 0).data.buffer);
}
const images = document.querySelectorAll('img');
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
for (const image of images) {
const pixels = reduceImage(image), counts = {};
var idx = pixels.length, maxPx, maxCount = 0;
while (idx-- > 0) {
const pixel = pixels[idx]; // get pixel
const count = counts[pixel] = counts[pixel] ? counts[pixel] + 1 : 1;
if (count > maxCount) {
maxCount = count;
maxPx = pixel;
if (count > pixels.length / 2) { break }
}
}
image._FOUND_DOMINATE_COLOR = pixel2CSScolor(maxPx);
}
});
Update
As there were some questions in the comments the next snippet is a check to make sure all is working.
I could not find any problems with the code apart from the correction I detailed in the comments.
I did change some names and increased the image reduction steps a lot more for reasons outlined under the next heading
Color frequency does not equal dominate color
The example below shows two images, when loaded the padding is set to the color found. You will note that the image on the right does not seem to get the color right.
This is because there are many browns yet no one brown is the most frequent.
In the my answer Finding dominant hue. I addressed the problem and found a solution that is more in tune with human perception.
Working example
. Warning for low end devices. one of the images is ~9Mpx .
addEventListener("load",() => { geMostFrequentColor() },{once: true});
const downScaleSteps = 4;
function geMostFrequentColor() {
const toHex = val => (val & 0xFF).toString(16).padStart(2,"0");
const pixel2CSScolor = px => `#${toHex(px >> 16)}${toHex(px >> 8)}${toHex(px)}`;
const reduceImage = image => {
var w = canvas.width = image.naturalWidth, h = canvas.height = image.naturalHeight, step = 0;
ctx.globalCompositeOperation = "source-over";
ctx.fillStyle = "#FFF";
ctx.fillRect(0, 0, w, h);
ctx.drawImage(image, 0, 0);
ctx.globalCompositeOperation = "copy";
while (step++ < downScaleSteps) {
ctx.drawImage(canvas, 0, 0, w, h, 0, 0, w /= 2, h /= 2);
}
return new Uint32Array(ctx.getImageData(0, 0, w | 0, h | 0).data.buffer);
}
const images = document.querySelectorAll('img');
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
var imgCount = 0;
for (const image of images) {
info.textContent = "Processing image: " + imgCount++;
const pixels = reduceImage(image), counts = {};
let idx = pixels.length, maxPx, maxCount = 0;
while (idx-- > 0) {
const pixel = pixels[idx]; // get pixel
const count = counts[pixel] = counts[pixel] ? counts[pixel] + 1 : 1;
if (count > maxCount) {
maxCount = count;
maxPx = pixel;
if (count > pixels.length / 2) { break }
}
}
image._MOST_FREQUENT_COLOR = pixel2CSScolor(maxPx);
image.style.background = image._MOST_FREQUENT_COLOR;
}
info.textContent = "All Done!";
}
img {
height: 160px;
padding: 20px;
}
<div id="info">Loading...</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/d/dd/Olympus-BX61-fluorescence_microscope.jpg" crossorigin="anonymous">
<img src="https://upload.wikimedia.org/wikipedia/commons/a/a5/Compound_Microscope_(cropped).JPG" alt="Compound Microscope (cropped).JPG" crossorigin="anonymous"><br>
Images from wiki no attribution required.
You're currently drawing an empty image. The image needs some time to load so you'll have to wait for that to happen.
Use the onload callback to draw the image to the canvas as soon as it has finished loading. Every other process should continue after this event.
img = new Image();
img.crossOrigin = "anonymous";
img.src = document.querySelectorAll('img')[i].src;
img.onload = function() {
c.clearRect(0, 0, canvasWidth, canvasHeight);
c.drawImage(img, 0, 0, canvasWidth, canvasHeight);
// Continue here
}

Detect radial Pixel

I Wan't to check the collision from radial Elements.
The Problem is, currently i check only the pixels as an rectangle because the other images are native with HTML-Elements.
I'm only using the canvas to draw the boundary background to check the alpha-transparency.
this.checkCollision = function checkCollision() {
var width = 34;
var height = 32;
var image = _context.getImageData(_position.x - (height / 2), _position.y - (width / 2), width, height);
var pixels = image.data;
var size = image.data.length;
// HERE I WANT TO CHECK A RADIAL AREA
for(var index = 0; index < size; index += 4) {
var RED = image.data[index];
var GREEN = image.data[index + 1];
var BLUE = image.data[index + 2];
var ALPHA = image.data[index + 3];
if(_debug) {
document.querySelector('#debug').innerHTML = JSON.stringify({
POSITION: _position,
INDEX: index,
COLOR: {
R: RED,
G: GREEN,
B: BLUE,
A: ALPHA
}
}, 0, 1);
}
if(ALPHA !== 0) {
return true;
}
}
_context.putImageData(image, 0, 0);
return false;
};
Preview
Here is a working Fiddle:
https://jsfiddle.net/2bLfd6xp/
How i can select a radial pixel range on getImageData to check the collision with the alpha-transparency from the boundary.png?
My idea is to modify the pixel data array from here:
var image = _context.getImageData(_position.x - (height / 2), _position.y - (width / 2), width, height);
and remove the invisible edges. But what is the best practice to calculate from an rectangle based pixel array an radial area to remove these unwanted pixels?
For sample:
var image = _context.getImageData(_position.x - (height / 2), _position.y - (width / 2), width, height);
var radial_area = selectRadialArea(image, radius);
function selectRadialArea(pixelArray, radius) {
/*
Modify `pixelArray` with given `radius`...
All pixels outside the `radius` filled with `null`...
*/
return theNewArray;
}
I've found the answer with logical thinking:
First, we create a temporary drawable context and draw in this new area two elemengts:
an red rectangle
an transparent arc/circle with an destination-composite
The resulted Uint8ClampedArray will be compared with the original Uint8ClampedArray. If the area is RED, we hide the pixels with null-entries:
this.rectangleToRadial = function rectangleToRadial(source, width, height) {
var test = document.createElement('canvas');
var context = test.getContext('2d');
// Create an transparent circle and a red removeable area
context.beginPath();
context.fillStyle = 'rgba(255, 0, 0, 1)';
context.fillRect(0, 0, width, height);
context.globalCompositeOperation = 'destination-out';
context.arc(width / 2, height / 2, width / 2, 0, 2 * Math.PI);
context.fillStyle = 'rgba(0, 0, 0, 1)';
context.fill();
// get the data
var destination = context.getImageData(0, 0, width, height);
var size = destination.data.length;
// check the pixels
for(var index = 0; index < size; index += 4) {
var RED = destination.data[index];
var GREEN = destination.data[index + 1];
var BLUE = destination.data[index + 2];
var ALPHA = destination.data[index + 3];
/*
if the >>red removeable area<< is given, null the pixel from the source
*/
if(RED == 255 && GREEN == 0 && BLUE == 0) {
// Remove this from source
source.data[index] = null;
source.data[index + 1] = null;
source.data[index + 2] = null;
source.data[index + 3] = null;
}
}
// Return the source `Uint8ClampedArray`
return source;
};
It was easy, when we try to think :)
var image = _context.getImageData(_position.x - (height / 2), _position.y - (width / 2), width, height);
var pixels = this.rectangleToRadial(image, width, height);

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

How to set image pattern to an Image in image tag

How to set image pattern to an Image in image tag.
I have found change image color using below code but I want to draw perticuler patterns(for e.g. Checks, strips, dotted etc) on the image.
I want solution for above concern,
Thanks in advance
function colorImage(imgId,hexaColor) {
// create hidden canvas (using image dimensions)
var imgElement = document.getElementById(imgId);
var canvas = document.createElement("canvas");
canvas.width = imgElement.width;
canvas.height = imgElement.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(imgElement,0,0);
var imageData = ctx.getImageData(0,0,canvas.width,canvas.height);
var data = imageData.data;
// convert image to grayscale
var rgbColor = hexToRgb(hexaColor);
for(var p = 0, len = data.length; p < len; p+=4) {
//if(data[p+3] == 0)
// continue;
data[p + 0] = rgbColor.r;
data[p + 1] = rgbColor.g;
data[p + 2] = rgbColor.b;
//data[p + 3] = 255;
}
ctx.putImageData(imageData, 0, 0);
// replace image source with canvas data
imgElement.src = canvas.toDataURL();
}
You would want to modify loop which does the drawing, for example this should produce some stripes:
for(var p = 0, len = data.length; p < len; p+=4) {
if ( (p % 8) == 0 ) {
data[p + 0] = 0;
data[p + 1] = 0;
data[p + 2] = 0;
} else {
data[p + 0] = 255;
data[p + 1] = 255;
data[p + 2] = 255;
}
}
Otherwise, you will need to get images with part of pattern and tile them like this:
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(),BitmapFactory.decodeResource(getResources(), R.drawable.tile_image));
bitmapDrawable.setTileModeX(TileMode.REPEAT);
imageView.setImageDrawable(bitmapDrawable);

Get a pixel from HTML Canvas?

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.

Categories

Resources