Canvas 使用 Uint32Array:渲染的颜色错误

Canvas using Uint32Array: Wrong colors are being rendered

我目前正在创建一个 JS canvas,我想在其中显示不同颜色的框。

我正在使用 uint32 来提高速度,但我的颜色始终无法正确显示! 我主要看了这里的例子: 有人在评论中说:

(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."

我确定我的笔记本电脑是小端的。

我在这里有我的问题的演示: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);

这里提到的颜色是:

但是 fiddle 显示黄色:

其他颜色也一样,先谢谢了!

编辑: 感谢@Oriol 的快速回答!我使用以下函数来反转我的颜色(以防有人感兴趣):

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);
};

像这样使用:reverseUint32(0xfc66feff)

当您将 Uint8Array 缓冲区视为 little endian 中的 Uint32 时,会发生这种情况:

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

因此在小端中,顺序变为 AABBGGRR 而不是 AARRGGBB。

您可以将 0x80d7ffff 反转为 0xffffd780,但这样就无法在大端计算机上运行了。

为避免这些问题,您可以使用 DataView,它允许指定字节顺序,默认为大端:

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>

不过浏览器好像没怎么优化DataView,所以比较慢。那么最好在 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>

关于性能。

最好的解决方案是进行字节序测试。

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;
    }
})();

根据测试写入像素。

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; 
}

性能一览

在使用(查看)写入一个值所需的时间内view.Uint32(i,val);您可以使用(直接)写入 33 个值data[i] = val;

来自 Firefox 的统计数据。

用于恒定循环时间测试。

  • 直接写入 * 每个周期 10,000 次:23222 97.07% 100%。
  • 视图写入 * 每个周期 10,000:702:2.93% 3.02%

第一个 % 是占总数的百分比。第二个 % 是最快的百分比(直接)

每 10,000 次写入的性能时间(时间以 1/1,000,000 秒为单位)

  • 直接1701个周期* 10000次写入。平均值:43.063(1e-6 秒)。方差:0.210
  • 查看 1701 个周期 * 10000 次写入。平均值:1424.832(1e-6 秒)。方差:379.441

请注意视图的高差异值是由于 Javascript 优化。通过 View 进行短时间的写入可能会慢得多。

对于每 10,000 次写入的性能时间(以 1/1,000,000 秒为单位的时间)允许方差稳定。

  • 直接 31636 周期 * 10000 次写入。平均值:29.981(1e-6 秒)。方差:0.005
  • 查看 31802 个周期 * 10000 次写入。平均值:982.195(1e-6 秒)。方差:0.154

更多测试

按照评论中的要求进行更多测试。测试单元是对其中一个测试函数的调用。因此,在以下测试中,每个测试单元写入 10000 次 32 位。

比较 8 位写入和 32 位写入

已测试功能

  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",
      }
  ];

共同背景

var i
var i,arr;
var data = new Uint8ClampedArray(100000);
var view2 = new Uint32Array(data.buffer);

测试结果原始转储。

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

查看和直接写入

本答案开头描述的测试 运行 的详细视图。

函数和共享上下文

 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",
      }
  ];

结果

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.