I'm currently creating a JS canvas where I want to display a box of different colors.
I'm using uint32 for extra speed, and my colors never display correctly! I've looked at the examples mainly over here: https://stackoverflow.com/a/19502117 where someone said in the comments:
(small I or JS will throw an error). Tip for OP: colors for Uint32 can also be given simply be using hex - no need to do shifting: 0xff00000 = black + alpha set to 255; for little-endian/LSB CPUs, opposite on big-endian/MSB CPUs."
I'm certain my laptop is little-endian.
I have a demo of my issue here: http://jsfiddle.net/GhwUC/357/
var canvas = document.getElementById('canvas');
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var ctx = canvas.getContext('2d');
var imageData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
var buf = new ArrayBuffer(imageData.data.length);
var buf8 = new Uint8ClampedArray(buf);
var data = new Uint32Array(buf);
for (var y = 0; y < canvasHeight; ++y) {
for (var x = 0; x < canvasWidth; ++x) {
data[y * canvasWidth + x] = 0xff80d7ff // Should be light blue (#80d7ff)
}
}
imageData.data.set(buf8);
ctx.putImageData(imageData, 0, 0);
The color in question here is:
But the fiddle displays a yellow-ish color:
It's the same on other colors, thanks a lot in advance!
EDIT: thanks @Oriol for quick answer! I used the following function to reverse my colors (in case anyone was interested):
function reverseUint32 (uint32) {
var s32 = new Uint32Array(4);
var s8 = new Uint8Array(s32.buffer);
var t32 = new Uint32Array(4);
var t8 = new Uint8Array(t32.buffer);
reverseUint32 = function (x) {
s32[0] = x;
t8[0] = s8[3];
t8[1] = s8[2];
t8[2] = s8[1];
t8[3] = s8[0];
return t32[0];
}
return reverseUint32(uint32);
};
Use it like: reverseUint32(0xfc66feff)
This happens when you treat a Uint8Array buffer as a Uint32 in little endian:
var buf = new Uint8Array([0x12, 0x34, 0x56, 0x78]).buffer;
console.log(new Uint32Array(buf)[0].toString(16));
// "78563412" in little endian, "12345678" in big endian
So in little endian, the order becomes AABBGGRR instead of AARRGGBB.
You could reverse 0x80d7ffff
to 0xffffd780
, but then it wouldn't work on big endian machines.
To avoid these problems you can use a DataView
, which allows to specify the endianness, defaulting to big endian:
view.setUint32(offset, 0xffffd780, true); // #80d7ff, in little endian
view.setUint32(offset, 0x80d7ffff, false); // #80d7ff, in big endian
var canvas = document.getElementById('canvas'),
canvasWidth = canvas.width,
canvasHeight = canvas.height,
ctx = canvas.getContext('2d'),
imageData = ctx.getImageData(0, 0, canvasWidth, canvasHeight),
view = new DataView(imageData.data.buffer);
for (var y = 0; y < canvasHeight; ++y) {
for (var x = 0; x < canvasWidth; ++x) {
var offset = 4 * (y * canvasWidth + x);
view.setUint32(offset, 0x80d7ffff); // light blue (#80d7ff)
}
}
ctx.putImageData(imageData, 0, 0);
<canvas id="canvas" height="256" width="256"></canvas>
But it seems that browsers haven't optimized much DataView
, so it's slow. Then it might be better to set the color components separately in the Uint8ClampedArray
:
var canvas = document.getElementById('canvas'),
canvasWidth = canvas.width,
canvasHeight = canvas.height,
ctx = canvas.getContext('2d'),
imageData = ctx.getImageData(0, 0, canvasWidth, canvasHeight),
data = imageData.data;
for (var y = 0; y < canvasHeight; ++y) {
for (var x = 0; x < canvasWidth; ++x) {
var offset = 4 * (y * canvasWidth + x);
// light blue (#80d7ff)
data[offset+0] = 0x80; // red
data[offset+1] = 0xd7; // green
data[offset+2] = 0xff; // blue
data[offset+3] = 0xff; // alpha
}
}
ctx.putImageData(imageData, 0, 0);
<canvas id="canvas" height="256" width="256"></canvas>
The best solution is to do an endian test.
var isLittleEndian = true;
(()=>{
var buf = new ArrayBuffer(4);
var buf8 = new Uint8ClampedArray(buf);
var data = new Uint32Array(buf);
data[0] = 0x0F000000;
if(buf8[0] === 0x0f){
isLittleEndian = false;
}
})();
The write the pixels depending on the test.
var imageData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
var data = new Uint32Array(imageData.data.buffer);
var val = 0xffffd780;
if(isLittleEndian){
val = 0x80d7ffff;
}
var i = 0;
while(i < data.length) {
data[i++] = val;
}
A look at performance
In the time it takes to write one value using (View) view.Uint32(i,val);
you can write 33 values using (Direct) data[i] = val;
Stats from Firefox.
For constant cycle time test.
First % is percent of total. Second % is percent of fastest (Direct)
For performance time per 10,000 writes (time in 1/1,000,000 seconds)
Note the high variance value for View this is due to Javascript optimisation. Doing short bursts of writes via View can be significantly slower.
For performance time per 10,000 writes (time in 1/1,000,000 seconds) allowing variance to stabilize.
As requested in comments some more tests. A test unit is a call to one of the test functions. Thus in the following tests 10000 32Bit writes per test unit.
Compare 8Bit writes to 32Bit writes
functions tested
testFunctions = [{
func:function(){
for(i = 0; i < 40000; i ++){
data[i++] = 0xFF;
data[i++] = 0xFF;
data[i++] = 0xd7;
data[i] = 0x80;
}},
name:"8Bit",
},{
func:function(){
for(i = 0; i < 10000; i ++){
view2[i] = 0x80d7ffff;
}},
name:"32Bit",
}
];
Common context
var i
var i,arr;
var data = new Uint8ClampedArray(100000);
var view2 = new Uint32Array(data.buffer);
Test results raw dump.
Test complete Results.
Function name, Calls per sec, % cycle time, % of best time
32Bit : 33743 : 76.84% 100%.
8Bit : 10170 : 23.16% 30.14%
Total cycles : 1000
Stable cycles : 899 Total.
Tests per cycle : 660
Testing cycles stable for : 800 of 800 cycles 100.00%
Max test variance 0.000%
Test results are good.
List of all test function results.
Mean times in micro secs 1/1,000,000 times mark with ms in milliseconds 1/1,000
# calls, total time, mean time
--------------------
Test function : 8Bit
62264 tests 6122.825ms Mean : 98
61942 tests 6088.945ms Mean : 98
62283 tests 6124.810ms Mean : 98
62233 tests 6121.010ms Mean : 98
Variance : 0.000micro sec. normalised : 0.000%
Ran : 248722 over 24457.590ms Mean : 98.333micro sec
--------------------
Test function : 32Bit
62084 tests 1839.180ms Mean : 30
61738 tests 1829.285ms Mean : 30
62282 tests 1846.225ms Mean : 30
62390 tests 1849.650ms Mean : 30
Variance : 0.000micro sec. normalised : 0.000%
Ran : 248494 over 7364.340ms Mean : 29.636micro sec
Total number of tests run : 497216
The View and Direct write
Detailed view of a test run described at the beginning of this answer.
Functions and shared context
sharedFunction = function(){
var i;
var data = new Uint8ClampedArray(100000);
var view1 = new DataView(data.buffer);
var view2 = new Uint32Array(data.buffer);
}
testFunctions = [{
func:function(){
for(i = 0; i < 10000; i ++){
view1.setUint32(i, 0x80d7ffff);
}
},
name:"View",
},{
func:function(){
for(i = 0; i < 10000; i ++){
view2[i] = 0x80d7ffff;
}},
name:"Direct",
}
];
Results
Test complete Results.
Calls per sec, % cycle time, % of best time
Direct : 35766 : 97.07% 100%.
View : 1080 : 2.93% 3.02%
Total cycles : 1000
Stable cycles : 899 Total.
Tests per cycle : 73
Testing cycles stable for : 800 of 800 cycles 100.00%
Max test variance 5.231%
Test results are good.
Mean times in micro secs 1/1,000,000 times mark with ms in milliseconds 1/1,000
# calls, total time, mean time
--------------------
Test function : View
8583 tests 7850.680ms Mean : 915
8454 tests 7830.950ms Mean : 926
8201 tests 7639.375ms Mean : 932
8459 tests 7883.150ms Mean : 932
Variance : 48.445micro sec. normalised : 5.231%
Ran : 33697 over 31204.155ms Mean : 926.022micro sec
--------------------
Test function : Direct
8434 tests 235.295ms Mean : 28
8347 tests 234.190ms Mean : 28
8451 tests 237.045ms Mean : 28
8260 tests 229.900ms Mean : 28
Variance : 0.009micro sec. normalised : 0.033%
Ran : 33492 over 936.430ms Mean : 27.960micro sec
Total number of tests run : 67189
Note Each test function is run as 4 separate functions. A stable test is when all 4 current cycle test times match the previous test cycle times. Javascript optimization will cause times to vary and as there is no way to know for sure when optimisation is happening the testing code waits till all test functions return a stable time at least 100 cycles. Further cycle time instability will be shown in the variance values.
Tests per cycle is an average (not noted in results)
All test functions are run in random order via
testFunction[ Math.floor(Math.random() * testFunctionCount * testsPerFunction) ]();
Timing is via
performance.now();
and measures only the inner content of the test functions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With