I have a BGRA array and need to draw it to a canvas.
Currently i was doing it like this:
var aVal = returnedFromChromeWorker;
var can = doc.createElementNS(NS_HTML, 'canvas');
can.width = aVal.width;
can.height = aVal.height;
var ctx = can.getContext('2d');
ctx.putImageData(aVal, 0, 0);
doc.documentElement.appendChild(can);
Is there some way to get a BGRA array onto the canvas? I was exploring: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/imgIEncoder
I can't re-order the array because my goal is to take screenshots and for large screens even just 1280x1024, it takes 2.3s to go through and re-order it all.
I tried re-ordering on the ctypes side but it's giving me quirky issues: 0, making the whole image invisible >_< lol BITMAPV5HEADER getting RGBA keep A at 255
How to put BGRA array into canvas without re-ordering
There is none.
Reorganize the byte-order is necessary as canvas can only hold data in RGBA format (little-endian, ie. ABGR in the buffer). Here is one way to do this:
You could add an extra step for your worker to deal with the reordering. Create a DataView for the raw byte buffer (ArrayBuffer), then iterate each Uint32 value.
Below a Uint32 is read as little-endian. This is because in this case that format is easier to swap around as we only need to right-shift BGR and put A back in front. If your original buffer is in big-endian you will of course need to read it as big-endian and set back as little-endian (getUint32(pos, false)):
Example
var uint32 = new Uint32Array(1), pos = 0; // create some dummy data
var view = new DataView(uint32.buffer); // create DataView for byte-buffer
var pos = 0; // byte-position (we'll skip 4 bytes each time)
// dummy data in BGRA format
uint32[0] = 0x7722ddff; // magenta-ish in BGRA format
document.write("BGRA: 0x" + (uint32[0]).toString(16) + "<br>");
// --- Iterate buffer, for each: ---
var v = view.getUint32(pos, true); // BGRA -> RGBA, read as little-endian
var n = (v >>> 8) | (v << 24); // rotate - move A from last to first position
view.setUint32(pos, n, true); // set back
pos += 4; // do this when inside the loop
// result
document.write("ABGR: 0x" + (uint32[0]>>>0).toString(16));
Update If the byte-order (endian-wise) is the same in both end you can skip the DataView and use Uint32Array directly which will speed things up a tad as well:
var uint32 = new Uint32Array(1), pos = 0; // create some dummy data
// inside loop:
var v = uint32[pos];
uint32[pos++] = (v >>> 8) | (v << 24); // pos index is now per uint32
Related
I am making a Three.js application that needs to download some depth data. The data consist of 512x256 depth entries, stored in a compressed binary format with the precision of two bytes each. The data must be readable from the CPU, so I cannot store the data in a texture. Floating point textures is not supported on many browsers anyway, such as Safari on iOS.
I have this working in Unity, but I am not sure how to go about downloading compressed depth like this using javascript / three.js. I am new to javascript, but seems it has limited support for binary data handling and compression.
I was thinking of switching to a textformat, but then memory footprint and download size is a concern. The user could potentially have to load hundreds of these depth buffers.
Is there a better way to download a readable depth buffer?
You can download a file as binary data with fetch and async/await
async function doIt() {
const response = await fetch('https://webglfundamentals.org/webgl/resources/eye-icon.png');
const arrayBuffer = await response.arrayBuffer();
// the data is now in arrayBuffer
}
doIt();
After that you can make TypedArray views to view the data.
async function doIt() {
const response = await fetch('https://webglfundamentals.org/webgl/resources/eye-icon.png');
const arrayBuffer = await response.arrayBuffer();
console.log('num bytes:', arrayBuffer.byteLength);
// arrayBuffer is now the binary data. To access it make one or more views
const bytes = new Uint8Array(arrayBuffer);
console.log('first 4 bytes:', bytes[0], bytes[1], bytes[2], bytes[3]);
const decoder = new TextDecoder();
console.log('bytes 1-3 as unicode:', decoder.decode(bytes.slice(1, 4)));
}
doIt();
As for a format for depth data that's really up to you. Assuming your format was just 16bit values representing ranges of depths from min to max
uint32 width
uint32 height
float min
float max
uint16 data[width * height]
Then after you've loaded the data you can use either muliplte array views.
const uint32s = new Uint32Array(arrayBuffer);
const floats = new Float32Array(arrayBuffer, 8); // skip first 8 bytes
const uint16s = new Uint16Array(arrayBuffer, 16); // skip first 16 bytes
const width = uint32s[0];
const height = uint32s[1];
const min = floats[0];
const max = floats[1];
const range = max - min;
const depthData = new Float32Array(width * height);
for (let i = 0; i < uint16s.length; ++i) {
depthData[i] = uint16s[i] / 0xFFFF * range + min;
}
If you care about endianness for some future world where there are any browsers running on big endian hardware, then you either write your own functions to read bytes and generate those values or you can use a DataView.
Assuming you know the data is in little endian format
const data = new DataView(arrayBuffer);
const width = data.getUint32(0, true);
const height = data.getUint32(4, true);
const min = data.getFloat32(8, true);
const max = data.getFloat32(12, true);
const range = max - min;
const depthData = new Float32Array(width * height);
for (let i = 0; i < uint16s.length; ++i) {
depthData[i] = data.getUint16(i * 2 + 16, true) / 0xFFFF * range + min;
}
Of you want more complex compression like a inflate/deflate file you'll need a library or to write your own.
It seems like there is nothing to handle endianness when working with UInt8. For example, when dealing with UInt16, you can set if you want little or big endian:
dataview.setUint16(byteOffset, value [, littleEndian])
vs
dataview.setUint8(byteOffset, value)
I guess this is because endianness is dealing with the order of the bytes and if I'm inserting one byte at a time, then I need to order them myself.
So how do I go about handling endianness myself? I'm creating a WAVE file header using this spec: http://soundfile.sapp.org/doc/WaveFormat/
The first part of the header is "ChunkID" in big endian and this is how I do it:
dataView.setUint8(0, 'R'.charCodeAt());
dataView.setUint8(1, 'I'.charCodeAt());
dataView.setUint8(2, 'F'.charCodeAt());
dataView.setUint8(3, 'F'.charCodeAt());
The second part of the header is "ChunkSize" in small endian and this is how I do it:
dataView.setUint8(4, 172);
Now I suppose that since the endianness of those chunks is different then I should be doing something different in each chunk. What should I be doing different in those two instances?
Cheers!
EDIT
I'm asking this question, because the wav file I'm creating is invalid (according to https://indiehd.com/auxiliary/flac-validator/). I suspect this is because I'm not handling the endianness correctly. This is the full wave file:
const fs = require('fs');
const BITS_PER_BYTE = 8;
const BITS_PER_SAMPLE = 8;
const SAMPLE_RATE = 44100;
const NB_CHANNELS = 2;
const SUB_CHUNK_2_SIZE = 128;
const chunkSize = 36 + SUB_CHUNK_2_SIZE;
const blockAlign = NB_CHANNELS * (BITS_PER_SAMPLE / BITS_PER_BYTE);
const byteRate = SAMPLE_RATE * blockAlign;
const arrayBuffer = new ArrayBuffer(chunkSize + 8)
const dataView = new DataView(arrayBuffer);
// The RIFF chunk descriptor
// ChunkID
dataView.setUint8(0, 'R'.charCodeAt());
dataView.setUint8(1, 'I'.charCodeAt());
dataView.setUint8(2, 'F'.charCodeAt());
dataView.setUint8(3, 'F'.charCodeAt());
// ChunkSize
dataView.setUint8(4, chunkSize);
// Format
dataView.setUint8(8, 'W'.charCodeAt());
dataView.setUint8(9, 'A'.charCodeAt());
dataView.setUint8(10, 'V'.charCodeAt());
dataView.setUint8(11, 'E'.charCodeAt());
// The fmt sub-chunk
// Subchunk1ID
dataView.setUint8(12, 'f'.charCodeAt());
dataView.setUint8(13, 'm'.charCodeAt());
dataView.setUint8(14, 't'.charCodeAt());
// Subchunk1Size
dataView.setUint8(16, 16);
// AudioFormat
dataView.setUint8(20, 1);
// NumChannels
dataView.setUint8(22, NB_CHANNELS);
// SampleRate
dataView.setUint8(24, ((SAMPLE_RATE >> 8) & 255));
dataView.setUint8(25, SAMPLE_RATE & 255);
// ByteRate
dataView.setUint8(28, ((byteRate >> 8) & 255));
dataView.setUint8(29, byteRate & 255);
// BlockAlign
dataView.setUint8(32, blockAlign);
// BitsPerSample
dataView.setUint8(34, BITS_PER_SAMPLE);
// The data sub-chunk
// Subchunk2ID
dataView.setUint8(36, 'd'.charCodeAt());
dataView.setUint8(37, 'a'.charCodeAt());
dataView.setUint8(38, 't'.charCodeAt());
dataView.setUint8(39, 'a'.charCodeAt());
// Subchunk2Size
dataView.setUint8(40, SUB_CHUNK_2_SIZE);
// Data
for (let i = 0; i < SUB_CHUNK_2_SIZE; i++) {
dataView.setUint8(i + 44, i);
}
A single byte (uint8) doesn't have any endianness, endianness is a property of a sequence of bytes.
According to the spec you linked, the ChunkSize takes space for 4 bytes - with the least significant byte first (little endian). If your value is only one byte (not larger than 255), you would just write the byte at offset 4 as you did. If the 4 bytes were in big endian order, you'd have to write your byte at offset 7.
I would however recommend to simply use setUint32:
dataView.setUint32(0, 0x52494646, false); // RIFF
dataView.setUint32(4, 172 , true);
dataView.setUint32(8, 0x57415645, false) // WAVE
I need to analyze an image to find all colors in a PNG or GIF image. I currently load the image to a canvas, then get the image data, then loop through every pixel and check it against each color in the palette. It takes forever, the browser thinks the script has stopped and it sometimes just crashes. Hoping there is a better way.
//load file
var fileReader = new FileReader();
fileReader.onload = function(e) {
var img = new Image();
img.onload = function() {
//create a new pixel with the images dimentions
newPixel(this.width, this.height, []);
//draw the image onto the canvas
context.drawImage(img, 0, 0);
var colorPalette = [];
var imagePixelData = context.getImageData(0,0,this.width, this.height).data;
console.log(imagePixelData)
for (var i = 0; i < imagePixelData.length; i += 4) {
var color = rgbToHex(imagePixelData[i],imagePixelData[i + 1],imagePixelData[i + 2]);
if (colorPalette.indexOf(color) == -1) {
colorPalette.push(color);
//don't allow more than 256 colors to be added
if (colorPalette.length >= settings.maxColorsOnImportedImage) {
alert('The image loaded seems to have more than '+settings.maxColorsOnImportedImage+' colors.')
break;
}
}
}
createColorPalette(colorPalette, false);
//track google event
ga('send', 'event', 'Pixel Editor Load', colorPalette.length, this.width+'/'+this.height); /*global ga*/
};
img.src = e.target.result;
};
fileReader.readAsDataURL(this.files[0]);
This will speed things up a little.
The function indexof will search all of the array if it can not find an entry. This can be very slow, you can improve the speed of the search by adding to the top of the array moving down then searching for the index from the top most entry. This ensures that the search is only over added entries not the entire array.
Also use 32Bit words rather than 8bit bytes, use typed arrays as they are significantly quicker that pushing onto a array and don't convert to hex inside the loop it is redundant and can be done after on the smaller data set.
See comments for logic.
//draw the image onto the canvas
ctx.drawImage(image, 0, 0);
// get size
const size = image.width * image.height;
// create pallete buffer
const colors32 = new Uint32Array(256);
// current pos of palette entry
var palettePos = colors32.length - 1; // Start from top as this will speed up indexOf function
// pixel data as 32 bit words so you can handle a pixel at a time rather than bytes.
const imgData = new Uint32Array(ctx.getImageData(0, 0, image.width, image.height).data.buffer);;
// hold the color. If the images are low colour (less 256) it is highly probable that many pixels will
// be the same as the previous. You can avoid the index search if this is the case.
var color= colors32[palettePos --] = imgData[0]; // assign first pixels to ease logic in loop
for (var i = 1; i < size && palettePos >= 0; i += 1) { // loop till al pixels read if palette full
if(color !== imgData[i]){ // is different than previouse
if (colors32.indexOf(imgData[i], palettePos) === -1) { // is in the pallet
color = colors32[palettePos --] = imgData[i]; // add it
}
}
}
// all the performance issues are over so now convert to the palette format you wanted.
const colorPalette = [];
colors32.reverse();
const paletteSize = (255 - palettePos) * 4;
const colors8 = new Uint8Array(colors32.buffer);
for(i = 0; i < paletteSize; i += 4){
colorPalette.push(rgbToHex(colors8[i],colors8[i + 1],colors8[i + 2]));
}
The Atomics.store/load methods (and others? didn't look) do not support Float32Array.
I read that this is to be consistent with the fact that it also doesn't support Float64Array for compatibility reasons (some computers don't support it).
Aside from the fact that I think this is stupid, does this also mean I must cast every float I want to use into an unsigned int?
Not only will this result in ugly code, it will also make it slower.
E.g.:
let a = new Float32Array(1); // Want the result here
Atomics.store(a, 0, 0.5); // Oops, can't use Float32Array
let b = new Float32Array(1); // Want the result here
let uint = new Uint32Array(1);
let float = new Float32Array(uint.buffer);
float[0] = 0.5;
Atomics.store(b, 0, uint[0]);
As you discovered, the Atomics methods doesn't support floating point values as argument:
Atomics.store(typedArray, index, value)
typedArray
A shared integer typed array. One of Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array,
or Uint32Array.
You can can read the IEEE754 representation as integer from the underlying buffer as you do in the example code you posted
var buffer = new ArrayBuffer(4); // common buffer
var float32 = new Float32Array(buffer); // floating point
var uint32 = new Uint32Array(buffer); // IEEE754 representation
float32[0] = 0.5;
console.log("0x" + uint32[0].toString(16));
uint32[0] = 0x3f000000; /// IEEE754 32-bit representation of 0.5
console.log(float32[0]);
or you can use fixed numbers if the accuracy isn't important. The accuracy is of course determined by the magnitude.
Scale up when storing:
Atomics.store(a, 0, Math.round(0.5 * 100)); // 0.5 -> 50 (max two decimals with 100)
read back and scale down:
value = Atomics.load(a, 0) * 0.01; // 50 -> 0.5
The other answer didn't help me much and it took awhile for me to figure out a solution, but here's how I solved the same issue:
var data = new SharedArrayBuffer(LEN * 8);
var data_float = new Float32Array(data);
var data_int = new Uint32Array(data);
data_float[0] = 2.3; //some pre-existing data
var tmp = new ArrayBuffer(8);
var tmp_float = new Float32Array(tmp);
var tmp_int = new Uint32Array(tmp);
tmp_int[0] = Atomics.load(data_int, 0);
tmp_float[0] += 1.1; //some math
Atomics.store(data_int, 0, tmp_int[0]);
console.log(data_float[0]);
Hi there I need function to calculate unique integer number from number (real number double precision) and integer.
Try explain I am developing GIS application in javascript and I am working with complex vector object like polygon (array of points object with two coordinate in ring) and lines array of points. I need fast algorithm to recognize that element has been changed it must be really fast because my vector object is collection of thousand points . In C# I am calculating hash code from coordinate using bitwise operation XOR.
But javascript convert all operands in bitwise operation to integer but i need convert double precision to integer before apply bitwise in c# way (binnary). In reflector i see this that c# calculate hash code fro double like this and I need this function in javascript as fast as can be.
public override unsafe int GetHashCode() //from System.Double
{
double num = this;
if (num == 0.0)
{
return 0;
}
long num2 = *((long*) &num);
return (((int) num2) ^ ((int) (num2 >> 32)));
}
Example:
var rotation = function (n) {
n = (n >> 1) | ((n & 0x001) << 31);
return n;
}
var x: number = 1;
var y: number = 5;
var hash = x ^ rotation(y); // result is -2147483645
var x1: number = 1.1;
var y1: number = 5;
var hash1 = x1 ^ rotation(y1); // result is -2147483645
Example result is not correct hash == hash1
Example 2: Using to string there is correct result but calculate Hash from string is to complicate and I thing is not fast enough.
var rotation = function (n) {
n = (n >> 1) | ((n & 0x001) << 31);
return n;
}
var GetHashCodeString = function(str: string): number {
var hash = 0, i, l, ch;
if (str.length == 0) return hash;
for (i = 0, l = str.length; i < l; i++) {
ch = str.charCodeAt(i);
hash = ((hash << 5) - hash) + ch;
hash |= 0; // Convert to 32bit integer
}
return hash;
}
var x: number = 1;
var y: number = 5;
var hash = GetHashCodeString(x.toString()) ^ rotation(GetHashCodeString(y.toString()));
//result is -2147483605
var x1: number = 1.1;
var y1: number = 5;
var hash1 = GetHashCodeString(x1.toString()) ^ rotation(GetHashCodeString(y1.toString()));
//result is -2147435090
Example2 result is correct hash != hash1
Is there some faster way than converting number to string than calculate hash from each character? Because my object is very large and it will take lot of time and operation in this way ...
I try do it using TypedArrays but yet I am not successful.
Thanks very much for your help
Hi there I tried use TypedArrays to calculate Hash code from number and the result is interesting. In IE the performance 4x better in Chrome 2x in FireFox this approach is equal to string version ...
var GetHashCodeNumber = function (n: number): number {
//create 8 byte array buffer number in js is 64bit
var arr = new ArrayBuffer(8);
//create view to array buffer
var dv = new DataView(arr);
//set number to buffer as 64 bit float
dv.setFloat64(0, n);
//now get first 32 bit from array and convert it to integer
// from offset 0
var c = dv.getInt32(0);
//now get next 32 bit from array and convert it to integer
//from offset 4
var d = dv.getInt32(4);
//XOR first end second integer numbers
return c ^ d;
}
I think this can be useful for someone
EDIT: using one buffer and DataView is faster !
Here is a faster way to do this in JavaScript.
const kBuf = new ArrayBuffer(8);
const kBufAsF64 = new Float64Array(kBuf);
const kBufAsI32 = new Int32Array(kBuf);
function hashNumber(n) {
// Remove this `if` if you want 0 and -0 to hash to different values.
if (~~n === n) {
return ~~n;
}
kBufAsF64[0] = n;
return kBufAsI32[0] ^ kBufAsI32[1];
}
It's 250x faster than the DataView approach: see benchmark.
I looked up some hashing libraries to see how they did it: xxhashjs, jshashes, etc.
Most seem to take a string or an ArrayBuffer, and also depend on UINT32-like functionality. This is equivalent to you needing a binary representation of the double (from your C# example). Notably I did not find any solution that included more-strange types, other than in another (unanswered) question.
His solution uses a method proposed here, which converts it to various typed arrays. This is most likely what you want, and the fastest accurate solution (I think).
I highly recommend that you structure your code to traverse objects/arrays as desired, and also benchmark the solution to see how comparable it is to your existing methods (the non-working one and the string one).