2D array to ImageData - javascript

So I have a 2D array created like this:
//Fill screen as blank
for(var x = 0; x<500; x++ ){
screen[x] = [];
for(var y = 0; y<500; y++ ){
screen[x][y] = '#ffffff';
}
}
And was wondering if there's an easy way to convert it to an ImageData object so I can display it on a canvas?

Flattening arrays
The first thing you'll have to learn is how to flatten a 2d array. You can use a nested loop and push to a new 1d array, but I prefer to use reduce and concat:
const concat = (xs, ys) => xs.concat(ys);
console.log(
[[1,2,3],[4,5,6]].reduce(concat)
)
Now you'll notice quickly enough that your matrix will be flipped. ImageData concatenates row by row, but your matrix is grouped by column (i.e. [x][y] instead of [y][x]). My advice is to flip your nested loop around :)
From "#ffffff" to [255, 255, 255, 255]
You now have the tool to create a 1d-array of hex codes (screen.reduce(concat)), but ImageData takes an Uint8ClampedArray of 0-255 values! Let's fix this:
const hexToRGBA = hexStr => [
parseInt(hexStr.substr(1, 2), 16),
parseInt(hexStr.substr(3, 2), 16),
parseInt(hexStr.substr(5, 2), 16),
255
];
console.log(
hexToRGBA("#ffffff")
);
Notice that I skip the first "#" char and hard-code the alpha value to 255.
Converting from hex to RGBA
We'll use map to convert the newly created 1d array at once:
screen.reduce(concat).map(hexToRGBA);
2d again?!
Back to square one... We're again stuck with an array of arrays:
[ [255, 255, 255, 255], [255, 255, 255, 255], /* ... */ ]
But wait... we already know how to fix this:
const flattenedRGBAValues = screen
.reduce(concat) // 1d list of hex codes
.map(hexToRGBA) // 1d list of [R, G, B, A] byte arrays
.reduce(concat); // 1d list of bytes
Putting the data to the canva
This is the part that was linked to in the comments, but I'll include it so you can have a working example!
const hexPixels = [
["#ffffff", "#000000"],
["#000000", "#ffffff"]
];
const concat = (xs, ys) => xs.concat(ys);
const hexToRGBA = hexStr => [
parseInt(hexStr.substr(1, 2), 16),
parseInt(hexStr.substr(3, 2), 16),
parseInt(hexStr.substr(5, 2), 16),
255
];
const flattenedRGBAValues = hexPixels
.reduce(concat) // 1d list of hex codes
.map(hexToRGBA) // 1d list of [R, G, B, A] byte arrays
.reduce(concat); // 1d list of bytes
// Render on screen for demo
const cvs = document.createElement("canvas");
cvs.width = cvs.height = 2;
const ctx = cvs.getContext("2d");
const imgData = new ImageData(Uint8ClampedArray.from(flattenedRGBAValues), 2, 2);
ctx.putImageData(imgData, 0, 0);
document.body.appendChild(cvs);
canvas { width: 128px; height: 128px; image-rendering: pixelated; }

I suspect your example code is just that, an example, but just in case it isn't there are easier way to fill an area with a single color:
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, 500, 500);
But back to flattening the array. If performance is a factor you can do it in for example the following way:
(side note: if possible - store the color information directly in the same type and byte-order you want to use as converting from string to number can be relatively costly when you deal with tens of thousands of pixels - binary/numeric storage is also cheaper).
Simply unwind/flatten the 2D array directly to a typed array:
var width = 500, height = 500;
var data32 = new Uint32Array(width * height); // create Uint32 view + underlying ArrayBuffer
for(var x, y = 0, p = 0; y < height; y++) { // outer loop represents rows
for(x = 0; x < width; x++) { // inner loop represents columns
data32[p++] = str2uint32(array[x][y]); // p = position in the 1D typed array
}
}
We also need to convert the string notation of the color to a number in little-endian order (format used by most consumer CPUs these days). Shift, AND and OR operations are multiple times faster than working on string parts, but if you can avoid strings at all that would be the ideal approach:
// str must be "#RRGGBB" with no alpha.
function str2uint32(str) {
var n = ("0x" + str.substr(1))|0; // to integer number
return 0xff000000 | // alpha (first byte)
(n << 16) | // blue (from last to second byte)
(n & 0xff00) | // green (keep position but mask)
(n >>> 16) // red (from first to last byte)
}
Here we first convert the string to a number - we shift it right away to a Uint32 value to optimize for the compiler now knowing we intend to use the number in the following conversion as a integer number.
Since we're most likely on a little endian plaform we have to shift, mask and OR around bytes to get the resulting number in the correct byte order (i.e. 0xAABBGGRR) and OR in a alpha channel as opaque (on a big-endian platform you would simply left-shift the entire value over 8 bits and OR in an alpha channel at the end).
Then finally create an ImageData object using the underlying ArrayBuffer we just filled and give it a Uint8ClampedArray view which ImageData require (this has almost no overhead since the underlying ArrayBuffer is shared):
var idata = new ImageData(new Uint8ClampedArray(data32.buffer), width, height);
From here you can use context.putImageData(idata, x, y).
Example
Here filling with a orange color to make the conversion visible (if you get a different color than orange then you're on a big-endian platform :) ):
var width = 500, height = 500;
var data32 = new Uint32Array(width * height);
var screen = [];
// Fill with orange color
for(var x = 0; x < width; x++ ){
screen[x] = [];
for(var y = 0; y < height; y++ ){
screen[x][y] = "#ff7700"; // orange to check final byte-order
}
}
// Flatten array
for(var x, y = 0, p = 0; y < height; y++){
for(x = 0; x < width; x++) {
data32[p++] = str2uint32(screen[x][y]);
}
}
function str2uint32(str) {
var n = ("0x" + str.substr(1))|0;
return 0xff000000 | (n << 16) | (n & 0xff00) | (n >>> 16)
}
var idata = new ImageData(new Uint8ClampedArray(data32.buffer), width, height);
c.getContext("2d").putImageData(idata, 0, 0);
<canvas id=c width=500 height=500></canvas>

Related

Efficient way for in between merge of array

I have single channel pixel data in plain DataView format. So the size of the DataView is width*height. For rendering a bitmap from that data, I need to get 4-channel data if I'm right. My current test/naive implementation is like this.
const data = new Uint8ClampedArray(data);
const expanded = new Uint8ClampedArray(width * height * 4);
data.forEach((v, i) => {
expanded[i * 4] = v;
expanded[i * 4 + 1] = v;
expanded[i * 4 + 2] = v;
expanded[i * 4 + 3] = 255;
});
But that's obviously not performant. On 12 megapixel data, this takes something like 300ms. Is there a more efficient way to do a merge like this?
Or even better as a side question: Can I draw single channel bitmap on img tag or canvas?
You can boost the performance by filling your target array with 32 bit instead of 8 bit unsigned integers. In this case the 32 bit value holds the r, g, b and alpha value which you can send using a single instruction.
To better understand let's look at an example. Say we have the color #4080c4 with full alpha ff. On a little-endian processor architecture this corresponds to:
0xffc48040 == 4291067968
Alpha==0xff==255
Blue==0xc4==196
Green=0x80==128
Red==0x40==64
So the 8 bit unsigned integer values are 255, 64, 128 and 196 respectively. To make a 32 bit unsigned integer out of this 4 individual values we need to use bit-shifting.
If we look back at the hexadecimal number - 0xffc48040 - in a naive way, we can see that the ff alpha value is at the left. That means there are 24 bits before which in turn means that ff has to be shifted 24 bits to the left. If we apply the same logic to the remaining three values we end up with this:
let UI32LE = (Alpha << 24) | (Blue << 16) | (Green << 8) | Red;
The << is JavaScript's bitshift operator.
Please note that the order of the bits is important! As I mentioned, the above applies to little-endian. If it's a big-endian architecture the order is different:
let UI32BE = (Red << 24) | (Green << 16) | (Blue << 8) | Alpha;
Now if we take this technique and use it on something close to your use case we end up with this:
let canvas = document.getElementById("canvas");
let context = canvas.getContext("2d");
let width = canvas.width;
let height = canvas.height;
let dummyData = [];
for (let a = 0; a < width * height; a++) {
dummyData.push(parseInt(Math.random() * 255));
}
const data = new Uint8ClampedArray(dummyData);
let buffer = new ArrayBuffer(width * height * 4);
let dataView = new Uint32Array(buffer);
let expanded = new Uint8ClampedArray(buffer);
data.forEach((v, i) => {
dataView[i] = (255 << 24) | (v << 16) | (v << 8) | v;
});
let imageData = context.getImageData(0, 0, width, height);
imageData.data.set(expanded);
context.putImageData(imageData, 0, 0);
<canvas id="canvas" width="200" height="100"></canvas>

Closest Array in Array of Arrays

I have an array colors:
const colors = [
[0, 10, 56],
[40, 233, 247],
[50, 199, 70],
[255, 0, 0],
...
];
and a function reduceColor:
const reduceColor = pix => {
let lowdist = Number.MAX_SAFE_INTEGER;
let closestColor;
for (let color of colors) {
const dist = Math.abs(pix[0] - color[0]) +
Math.abs(pix[1] - color[1]) +
Math.abs(pix[2] - color[2]) ;
if (dist<lowdist) {
lowdist = dist;
closestColor = color;
}
}
return closestColor;
}
console.log(reduceColor([240, 10, 30]));
// [255, 0, 0]
Which works fine, but is slow in the context of a whole image.
Is there a way to, when supplied an array, check another array (made up of subarrays) for the closest subarray, without having to iterate and check over every subarray?
I don't think there is a better solution, you have to check that every row, but you could use Typed Arrays that has usually faster implementations, if you make a good use of their built-in methods. Usually browsers use these typed arrays in audio processing or canvas rendering.
Surely avoid JS Arrays because they are not contiguos in the memory, huge operations on them can be very slow. Typed arrays are contiguos instead.
Also avoid to use Math methods because they are implemented with the abstraction of JS interpreter objects, so Math.abs could be quite slow (it should handle big number, floating points, string, null, NaN, etc.). Numerical operations can be faster in these cases.
For the solution, you could use a Uint32Array (MDN), by reinterpreting the colors as a 32-bit number. In hexadecimal we represent a byte with 2 hex digits, so 32-bit is 8 digits. We will store them in the array as:
-- RR GG BB
0x 00 00 00 00
function makeColor(r, g, b) {
// the 32-bit color will be "0x00RRGGBB" in hex
return (r << 16) + (g << 8) + b;
}
function parseColor(color) {
// isolating the corrispondent bit of the colors
return [ (color & 0xff0000) >> 16, (color & 0xff00) >> 8, color & 0xff ];
}
function makeColorArray(colors) {
// creating the Uint32Array from the colors array
return new Uint32Array(colors.map(c => makeColor(...c)));
}
function distanceRGB(p1, p2) {
let r1 = (p1 & 0xff0000) >> 16, // extract red p1
g1 = (p1 & 0xff00) >> 8, // extract green p1
b1 = p1 & 0xff, // extract blue p1
r2 = (p2 & 0xff0000) >> 16, // extract red p2
g2 = (p2 & 0xff00) >> 8, // extract green p2
b2 = p2 & 0xff, // extract blue p2
rd = r1 > r2 ? (r1 - r2) : (r2 - r1), // compute dist r
gd = g1 > g2 ? (g1 - g2) : (g2 - g1), // compute dist g
bd = b1 > b2 ? (b1 - b2) : (b2 - b1); // compute dist b
return rd + gd + bd; // sum them up
}
// rising the threshold can speed up the process
function findNearest(colors, distance, threshold) {
return function(pixel) {
let bestDist = 765, best = 0, curr; // 765 is the max distance
for (c of colors) {
curr = distance(pixel, c);
if (curr <= threshold) { return c; }
else if (curr < bestDist) {
best = c;
bestDist = curr;
}
}
return best;
};
}
const colors = makeColorArray([
[0, 10, 56],
[40, 233, 247],
[50, 199, 70],
[255, 0, 0],
//... other colors
]);
const image = makeColorArray([
[240, 10, 30]
//... other pixels
])
const nearest = Array.from(image.map(findNearest(colors, distanceRGB, 0))).map(parseColor)
// ===== TEST =====
function randomColor() {
return [ Math.floor(Math.random() * 255), Math.floor(Math.random() * 255), Math.floor(Math.random() * 255) ];
}
testImages = [];
for (let i = 0; i < 1000000; ++i) { testImages.push(randomColor()); }
testImages = makeColorArray(testImages)
testColors = [];
for (let i = 0; i < 1000; ++i) { testColors.push(randomColor()); }
testColors = makeColorArray(testColors);
// START
let testNow = Date.now();
Array.from(testImages.map(findNearest(testColors, distanceRGB, 0))).map(parseColor)
console.log(Date.now() - testNow)
I've tested it over a million random pixels (1000x1000) with 4 colors and takes less then a second (~250-450ms), with a thousand of test colors takes 12-15 seconds. The same experiment with a normal Array took 4-6 seconds just with 4 colors, I did not try the thousand colors test (all on my PC obviously).
Consider to pass heavy work to a Worker (MDN) to avoid UI freezing.
I don't know if it is enough to be runned on a bigger image. I'm sure it can be further optimized (maybe through Gray code hamming distance), but it is a good start point by the way.
You can also be interested to get a way to extract pixels values from images, just check ImageData (MDN) and how to retrive it (Stack Overflow) from a <img> or <canvas> element.

Operations on canavs image in js frozen browser

I wrote js script which performs various operations (eg. summing up photos with a constant, square root, moving, applying filters) on the pictures in the canvas. But for large images (eg. 2000x200 pixels), the script frozen/crashes the browser (tested on Firefox), in addition, everything takes a long time.
function get_pixel (x, y, canvas)
{
var ctx = canvas.getContext("2d");
var imgData = ctx.getImageData(x, y, 1, 1);
return imgData.data;
}
function set_pixel (x, y, canvas, red, green, blue, alpha)
{
var ctx = canvas.getContext('2d');
var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height),
pxData = imgData.data,
length = pxData.length;
var i = (x + y * canvas.width) * 4;
pxData[i] = red;
pxData[i + 1] = green;
pxData[i + 2] = blue;
pxData[i + 3] = alpha;
ctx.putImageData (imgData, 0, 0);
}
function sum (number, canvas1, canvas2)
{
show_button_normalization (false);
asyncLoop(
{
length : 5,
functionToLoop : function(loop, i){
setTimeout(function(){
asyncLoop(
{
length : 5,
functionToLoop : function(loop, i){
setTimeout(function(){
var pixel1 = get_pixel (i, j, canvas1);
var pixel2;
if (canvas2 != null)
{
pixel2 = get_pixel (i, j, canvas2);
}
else
{
pixel2 = new Array(4);
pixel2[0] = number;
pixel2[1] = number;
pixel2[2] = number;
pixel2[3] = number;
}
var pixel = new Array(4);
pixel[0] = parseInt (parseInt (pixel1[0]*0.5) + parseInt (pixel2[0]*0.5));
pixel[1] = parseInt (parseInt (pixel1[1]*0.5) + parseInt (pixel2[1]*0.5));
pixel[2] = parseInt (parseInt (pixel1[2]*0.5) + parseInt (pixel2[2]*0.5));
pixel[3] = parseInt (parseInt (pixel1[3]*0.5) + parseInt (pixel2[3]*0.5));
set_pixel (i, j, image1_a, pixel[0], pixel[1], pixel[2], pixel[3]);
loop();
},1000);
},
});
loop();
},1000);
},
});
/*for (var i=0; i<canvas1.width; i++)
{
for (var j=0; j<canvas1.height; j++)
{
var pixel1 = get_pixel (i, j, canvas1);
var pixel2;
if (canvas2 != null)
{
pixel2 = get_pixel (i, j, canvas2);
}
else
{
pixel2 = new Array(4);
pixel2[0] = number;
pixel2[1] = number;
pixel2[2] = number;
pixel2[3] = number;
}
var pixel = new Array(4);
pixel[0] = parseInt (parseInt (pixel1[0]*0.5) + parseInt (pixel2[0]*0.5));
pixel[1] = parseInt (parseInt (pixel1[1]*0.5) + parseInt (pixel2[1]*0.5));
pixel[2] = parseInt (parseInt (pixel1[2]*0.5) + parseInt (pixel2[2]*0.5));
pixel[3] = parseInt (parseInt (pixel1[3]*0.5) + parseInt (pixel2[3]*0.5));
set_pixel (i, j, image1_a, pixel[0], pixel[1], pixel[2], pixel[3]);
}
}*/
}
Is it possible to fix it?
Process pixels together!!
Looking at the code I would say that Firefox crashing and/or taking a long time is not a surprise at all. An image that is 2000 by 2000 pixels has 4 million pixels. I don't know what asyncLoop does but to me it looks like you are using timers to set groups of 5 pixels at a time. This is horrifically inefficient.
Problems with your code
Even looking at the commented code (which I assume is an alternative approch) you are processing the pixels with way to much overhead.
The array pixel you get from the function getPixel which returns the pixel array that is part of the object getImageData returns. If you look at the details of getImageData and te return object imageData you will see that the array is a typed array of type Uint8ClampedArray
That means most of the code you use to mix the pixels is redundant as that is done by javascript automatically when it assigns a number to any typed array.
pixel[0] = parseInt (parseInt (pixel1[0]*0.5) + parseInt (pixel2[0]*0.5));
Will be much quicker if you use
pixel[0] = (pixel1[0] + pixel2[0]) * 0.5; // a * n + b * n is the same as ( a+ b) *n
// with one less multiplication.
Standard simple image processing
But even then using a function call for each pixel adds a massive overhead to the basic operation you are performing. You should fetch all the pixels in one go and process them as two flat arrays.
Your sum function should look more like
function sum (number, canvas1, canvas2){
var i, data, ctx, imgData, imgData1, data1;
ctx = canvas1.getContext("2d");
imgData = ctx.getImageData(0, 0, canvas1.width, canvas1.height);
data = imgData.data; // get the array of pixels
if(canvas2 === null){
i = data.length;
number *= 0.5; // pre calculate number
while(i-- > 0){
data[i] = data[i] * 0.5 + number;
}
}else{
if(canvas1.width !== canvas2.width || canvas1.height !== canvas2.height){
throw new RangeError("Canvas size miss-match, can not process data as requested.");
}
data1 = canvas2.getContext("2d").getImageData(0,0,canvas2.width, canvas2.height).data
i = data.length;
while(i-- > 0){
data[i] = (data[i] + data1[i]) * 0.5;
}
}
ctx.setImageData(imgData,0,0); // put the new pixels back to the canvas
}
Bit math is quicker
You can improve on that if you use a bit of bit manipulation. Using a 32 bit typed array you can divide then add four 8 bit values in parallel (4* approx quicker for pixel calculations).
Note that this method will round down by one value a little more often than it should. ie Math.floor(199 * 233) === 216 is true while the method below will return 215. This can be corrected for by using the bottom bit of both inputs to add to the result. This completely eliminates the rounding error but the processing cost in my view is not worth the improvement. I have included the fix as commented code.
Note this method will only work for a / n + b / m where n and m are equal to 2^p and p is an integer > 0 and < 7 (in other words only if n and m are 2,4,8,16,32,64,127) and you must mask out the bottom p bits for a and b
Example performs C = C * 0.5 + C1 * 0.5 when C and C1 represent each R,G,B,A channel for canvas1 and canvas2
function sum (number, canvas1, canvas2){
var i, data, ctx, imgData, data32, data32A;
// this number is used to remove the bottom bit of each color channel
// The bottom bit is redundant as divide by 2 removes it
const botBitMask = 0b11111110111111101111111011111110;
// mask for rounding error (not used in this example)
// const botBitMaskA = 0b00000001000000010000000100000001;
ctx = canvas1.getContext("2d");
imgData = ctx.getImageData(0, 0, canvas1.width, canvas1.height);
data32 = new Uint32Array(imgData.data.buffer);
i = data32.length; // get the length that is 1/4 the size
if(canvas2 === null){
number >>= 1; // divide by 2
// fill to the 4 channels RGBA
number = (number << 24) + (number << 16) + (number << 8) + number;
// get reference to the 32bit version of the pixel data
while(i-- > 0){
// Remove bottom bit of each channel and then divide each channel by 2 using zero fill right shift (>>>) then add to number
data32[i] = ((data32[i] & botBitMask) >>> 1) + number;
}
}else{
if(canvas1.width !== canvas2.width || canvas1.height !== canvas2.height){
throw new RangeError("Canvas size miss-match, can not process data as requested.");
}
data32A = new Uint32Array(canvas2.getContext("2d").getImageData(0,0,canvas2.width, canvas2.height).data.buffer);
i = data32.length;
while(i-- > 0){
// for fixing rounding error include the following line removing the second one. Do the same for the above loop but optimise for number
// data32[i] = (((data32[i] & botBitMask) >>> 1) + ((data32A[i] & botBitMask) >>> 1)) | ((data32[i] & botBitMaskA) | (data32A[i] & botBitMaskA))
data32[i] = ((data32[i] & botBitMask) >>> 1) + ((data32A[i] & botBitMask) >>> 1);
}
}
ctx.setImageData(imgData,0,0); // put the new pixels back to the canvas
}
With all that you should not have any major problems. Though you will still have the page blocked while the image is being processed (depending on the machine and the image size it may take up to a second or 2)
Other solutions.
If you want to stop the image processing from blocking the page you can use a web worker and just send the data to them to process synchronously. You can find out how to do that but just searching stackOverflow.
Or use WebGL to process the images.
And you have one final option. The canvas api uses the GPU to do all its rendering and if you understand the way blending and compositing works you can do a surprising amount of maths using the canvas.
For example you can multiply all pixels RGBA channels with a value 0-1 using the following.
// multiplies all channels in source canvas by val and returns the resulting canvas
// returns the can2 the result of each pixel
// R *= val;
// G *= val;
// B *= val;
// A *= val;
function multiplyPixels(val, source)
var sctx = source.getContext("2d");
// need two working canvas. I create them here but if you are doing this
// many times you should create them once and reuse them
var can1 = document.createElement("canvas");
var can2 = document.createElement("canvas");
can1.width = can2.width = source.width;
can1.height= can2.height = source.height;
var ctx1 = can1.getContext("2d");
var ctx2 = can2.getContext("2d");
var chanMult = Math.round(255 * val);
// clamp it to 0-255 inclusive
chanMult = chanMult < 0 ? 0 : chanMult > 255 ? 255 : chanMult;
ctx1.drawImage(source,0,0); // copy the source
// multiply all RGB pixels by val
ctx1.fillStyle = "rgba(" + chanMult + "," + chanMult + "," + chanMult + ",1)";
ctx1.globalCompositeOperation = "multiply";
ctx1.fillRect(0, 0, source.width, source.height);
// now multiply the alpha channel by val. Clamp it to 0-1
ctx2.globalAlpha = val < 0 ? 0 : val > 1 ? 1 : val;
ctx2.drawImage(can1,0,0);
return can2;
}
There are quite a few composite operation that you can use in combination to do multiplication, addition, subtraction and division. Note though the accuracy is a little less than 8bits as addition and subtraction requires weighted values to compensate for the blending's (automatic) multiplication. Also the alpha channel must be handled separately from the RGB channels using globalAlpha and the compositing operations.
Realtime
The processing you are doing is very simple and a 2000 by 2000 pixel image can easily be processed in realtime. WebGl filter is an example of using webGL to do image processing. Though the filter system is not modular and the code is very old school it is a good backbone for webGL filters and offers much higher quality results because it uses floating point RGBA values.

Check if all pixels in a region are all empty in Javascript?

I have 2D canvas with things drawn to it and I want to know if all the pixels in a region (rect - x,y,w,h) are all empty/fully transparent? I know this can be done with getImageData but is there a faster way? I am writing a simple java script image packer and I wish to exclude the empty images from the final sheet.
The only way to read pixels is to use getImageData(), but you can speed this sort of checks up by using a different view than the default Uint8ClampedArray, for example Uint32Array which allows you to read a single pixel per iteration:
function isEmpty(ctx, x, y, w, h) {
var idata = ctx.getImageData(x, y, w, h), // needed as usual ...
u32 = new Uint32Array(idata.data.buffer), // reads 1x uint32 instead of 4x uint8
i = 0, len = u32.length;
while(i < len) if (u32[i++]) return false; // if !== 0 return false, not empty
return true // all empty, all OK
}
However, this cannot be used to check for transparency though. Even if a pixel is fully transparent there may be color data present in the other channels. For example, this would produce an invisible pixel: rgba(255,128,0,0) and isEmpty() would report the area to be non-empty even if the pixel isn't visible.
To check those cases you'll have to check the alpha channel only, and you could simply modify the above to use an AND mask to filter out color data, or, shift the alpha channel bits over, pushing the other bits out - in either case we are after non-0 values.
As this is in little-endian (LSB) format (as on most main stream computers nowadays), the components are in the order ABGR (0xAABBGGRR) so we can do either:
u32[i] & 0xff000000
or use shift (sign does not matter so much in this case, but personally I prefer to use unsigned shift (>>> rather than >>) when I deal with unsigned numbers to begin with):
u32[i]>>>24
Performance wise there is very little difference, I would guess ANDing is slightly faster if anything:
ANDing
function isTransparent(ctx, x, y, w, h) {
var idata = ctx.getImageData(x, y, w, h), // needed as usual ...
u32 = new Uint32Array(idata.data.buffer), // reads 1x uint32 instead of 4x bytes
i = 0, len = u32.length;
while(i < len) if (u32[i++] & 0xff000000) return false; // not transparent?
return true // all transparent, all OK
}
Bit-shifting
function isTransparent(ctx, x, y, w, h) {
var idata = ctx.getImageData(x, y, w, h), // needed as usual ...
u32 = new Uint32Array(idata.data.buffer), // reads 1x uint32 instead of 4x bytes
i = 0, len = u32.length;
while(i < len) if (u32[i++]>>>24) return false; // not transparent?
return true // all transparent, all OK
}
Update:
Speed up tricks
If you know that the data you're checking is of at least some size, lets say 2x2 pixels, you can also improve the speed by skipping every other pixel, even every other line:
while(i < len) if (u32[(i += 2)]>>>24) return false; // skips every 2. pixel
For lines, you need two iterators:
while(i < len) {
var endLine = i + width, p = i; // p in case you deal with odd widths
while(p < endLine) if (u32[(p += 2)]>>>24) return false; // skip every 2. pixel
i += width * 2; // skip a line
}

How to get the average or main color from an image with javascript?

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)

Categories