为什么我的画布杂讯功能总是只显示红色?

汤姆

我正在尝试根据我看到代码笔将噪波效果应用于画布,这反过来似乎与SO答案非常相似

我想产生一个随机透明像素的“屏幕”,但取而代之的是得到一个完全不透明的红色场。我希望对画布或类型数组更熟悉的人可以告诉我我做错了什么,也许可以帮助我了解一些正在使用的技术。


我对代码笔代码进行了重大重构,因为(暂时)我不关心动画噪声:

/**
 * apply a "noise filter" to a rectangular region of the canvas
 * @param {Canvas2DContext} ctx - the context to draw on
 * @param {Number} x - the x-coordinate of the top-left corner of the region to noisify
 * @param {Number} y - the y-coordinate of the top-left corner of the region to noisify
 * @param {Number} width - how wide, in canvas units, the noisy region should be
 * @param {Number} height - how tall, in canvas units, the noisy region should be
 * @effect draws directly to the canvas
 */
function drawNoise( ctx, x, y, width, height ) {
    let imageData = ctx.createImageData(width, height)
    let buffer32 = new Uint32Array(imageData.data.buffer)

    for (let i = 0, len = buffer32.length; i < len; i++) {
        buffer32[i] = Math.random() < 0.5
            ? 0x00000088 // "noise" pixel
            : 0x00000000 // non-noise pixel
    }

    ctx.putImageData(imageData, x, y)
}

据我所知,发生的核心是将ImageData的原始数据表示形式(一系列依次反映每个像素的红色,绿色,蓝色和alpha值的8位元素)包装在一个32位数组,使我们可以将每个像素作为一个统一的元组进行操作。我们得到一个数组,每个像素一个元素,而不是每个像素四个元素。

然后,我们遍历该数组中的元素,并根据噪声逻辑将RGBA值写入每个元素(即每个像素)。这里的噪声逻辑非常简单:每个像素都有大约50%的机会成为“噪声”像素。

噪声像素被分配了32位值0x00000088,该(由于数组提供的32位分块)等于rgba(0, 0, 0, 0.5),即黑色,不透明度为50%。

为非噪声像素分配了32位值0x00000000,该值为黑色0%不透明度,即完全透明。

有趣的是,我们不会将写入buffer32画布。取而代之的是,我们编写了imageData用于构造的Uint32Array,这使我相信我们正在通过某种传递引用来对imageData对象进行突变;我不清楚这是为什么。我知道值传递和引用传递在JS中通常是如何工作的(标量按值传递,对象按引用传递),但是在非类型化数组世界中,传递给数组构造函数的值仅决定数组的长度。显然这不是这里发生的事情。


如前所述,我得到的不是全部为50%或100%透明的黑色像素,而是一个全部为纯红色的像素。我不仅不希望看到红色,而且随机颜色分配的证据为零:每个像素都是纯红色。

通过使用两个十六进制值,我发现这会产生红色在黑色上的散射,并具有正确的分布类型:

buffer32[i] = Math.random() < 0.5
    ? 0xff0000ff // <-- I'd assume this is solid red
    : 0xff000000 // <-- I'd assume this is invisible red

但是它仍然是纯红色,纯黑色。基础画布数据均未显示出应不可见的像素。

令人困惑的是,除了红色或黑色之外,我没有其他颜色。除了100%不透明外,我也无法获得任何透明性。为了说明断开连接,我删除了random元素,并尝试将这9个值中的每个值写入每个像素,以查看会发生什么:

buffer32[i] = 0xRrGgBbAa
                         // EXPECTED   // ACTUAL
buffer32[i] = 0xff0000ff // red 100%   // red 100%
buffer32[i] = 0x00ff00ff // green 100% // red 100%
buffer32[i] = 0x0000ffff // blue 100%  // red 100%
buffer32[i] = 0xff000088 // red 50%    // blood red; could be red on black at 50%
buffer32[i] = 0x00ff0088 // green 50%  // red 100%
buffer32[i] = 0x0000ff88 // blue 50%   // red 100%
buffer32[i] = 0xff000000 // red 0%     // black 100%
buffer32[i] = 0x00ff0000 // green 0%   // red 100%
buffer32[i] = 0x0000ff00 // blue 0%    // red 100%

这是怎么回事?


编辑:与分配后,类似的(坏)结果Uint32Array和怪异的突变的基础上,对MDN文章ImageData.data

/**
 * fails in exactly the same way
 */
function drawNoise( ctx, x, y, width, height ) {
    let imageData = ctx.createImageData(width, height)

    for (let i = 0, len = imageData.data.length; i < len; i += 4) {
        imageData.data[i + 0] = 0
        imageData.data[i + 1] = 0
        imageData.data[i + 2] = 0
        imageData.data[i + 3] = Math.random() < 0.5 ? 255 : 0
    }

    ctx.putImageData(imageData, x, y)
}
海道

[TLDR]:

您的硬件的字节序设计为LittleEndian,因此正确的十六进制格式0xAABBGGRR不是0xRRGGBBAA


首先让我们解释TypedArrays:ArrayBuffers背后的“魔力

ArrayBuffer是一个非常特殊的对象,它直接链接到设备的内存。ArrayBuffer接口本身并没有太多功能,但是当您创建一个接口时,实际上是length在您的脚本中为其分配了内存。也就是说,js引擎不会处理重新分配,将其移动到其他位置,将其分块以及所有这些慢速操作的情况,就像通常的JS对象所做的那样。
因此,这使其成为处理二进制数据最快的对象之一。

但是,如前所述,其接口本身非常有限。我们无法直接从ArrayBuffer访问数据,为此,我们必须使用视图对象,该对象不会复制数据,但实际上只是提供了一种直接访问数据的方法。

您可以在同一个ArrayBuffer上拥有不同的视图,但是所使用的数据将始终只是ArrayBuffer之一,并且如果您确实从一个视图编辑ArrayBuffer,则从另一个视图可以看到它:

const buffer = new ArrayBuffer(4);
const view1 = new Uint8Array(buffer);
const view2 = new Uint8Array(buffer);

console.log('view1', ...view1); // [0,0,0,0]
console.log('view2', ...view2); // [0,0,0,0]

// we modify only view1
view1[2] = 125;

console.log('view1', ...view1); // [0,0,125,0]
console.log('view2', ...view2); // [0,0,125,0]

有不同类型的视图对象,每种视图对象将提供不同的方式来表示分配给ArrayBuffer分配的内存插槽的二进制数据。

TypedArrays像Uint8Array,Float32Array等是它们提供一个简单的方法来操纵数据作为数组,表示在自己的格式的数据(8位,浮点32等)ArrayLike接口。
数据视图界面允许更开放的操作一样,甚至无法正常无效边界以不同的格式读取,但是,它是以牺牲性能为代价。

所述接口的ImageData本身使用一个ArrayBuffer存储其像素数据。默认情况下,它将在此数据上公开一个Uint8ClampedArray视图也就是说,一个ArrayLike对象,对于每个通道Red,Green,Blue和Alpha,每个32位像素均表示为从0到255的值。

因此,您的代码利用了TypedArrays只是视图对象这一事实,而在基础ArrayBuffer上具有其他视图将直接对其进行修改。
它的作者选择使用Uint32Array,因为它是一种在单个镜头中设置完整像素(记住画布图像为32位)的方法。您可以减少四倍的工作量。

但是,这样做会开始处理32位值。这可能会有点问题,因为现在字节顺序很重要。
Uint8Array在BigEndian系统[0x00, 0x11, 0x22, 0x33]中将表示为32位值0x00112233,但0x33221100在LittleEndian系统中将表示为32bits

const buff = new ArrayBuffer(4);
const uint8 = new Uint8Array(buff);
const uint32 = new Uint32Array(buff);

uint8[0] = 0x00;
uint8[1] = 0x11;
uint8[2] = 0x22;
uint8[3] = 0x33;

const hex32 = uint32[0].toString(16);
console.log(hex32, hex32 === "33221100" ? 'LE' : 'BE');

请注意,大多数个人硬件都是LittleEndian,因此如果您的计算机也如此,也就不足为奇了。


因此,有了这些,我希望您知道如何修复代码:要生成颜色rgba(0,0,0,.5),您需要设置Uint32值0x80000000

drawNoise(canvas.getContext('2d'), 0, 0, 300, 150);

function drawNoise(ctx, x, y, width, height) {
  const imageData = ctx.createImageData(width, height)
  const buffer32 = new Uint32Array(imageData.data.buffer)

  const LE = isLittleEndian();
                  // 0xAABBRRGG : 0xRRGGBBAA;
  const black = LE ? 0x80000000 : 0x00000080;
  const blue  = LE ? 0xFFFF0000 : 0x0000FFFF;

  for (let i = 0, len = buffer32.length; i < len; i++) {
    buffer32[i] = Math.random() < 0.5

      ? black
      : blue
  }

  ctx.putImageData(imageData, x, y)
}
function isLittleEndian() {
  const uint8 = new Uint8Array(8);
  const uint32 = new Uint32Array(uint8.buffer);
  uint8[0] = 255;
  return uint32[0] === 0XFF;
}
<canvas id="canvas"></canvas>

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

为什么我的p:progressBar不显示,只显示数字?

为什么我的 Pandas DataFrame 只显示列名

我的卡只显示背面。为什么这样?

为什么将hsl blue转换回rgb时总是显示为红色?

为什么我的图标是红色的?

为什么我的搜索功能总是输出true?

为什么只显示波的顶部附近?我想显示整个波形

为什么图像不只显示字符

为什么此代码只显示零?

为什么我的图像未显示在Tkinter画布中

为什么我的 React 组件总是显示相同的状态?

为什么我的“通知通知”图标总是显示为灰色?

为什么我的Jquery Slide div总是始终显示在开头

为什么我的python程序总是显示相反的反应

为什么我的momentjs总是在03显示分钟?

为什么我的功能给我“这不是红色”?

为什么我的IdentitySet只显示最后一个项目?

为什么我的 JavaScript 文件只显示在 Chrome 开发者工具中的包中?

为什么在heroku上部署时只显示我网站上的主页?

为什么我的ComboBox只显示第一个结果?

为什么我的“回声”只显示1个字?(Linux-Bash)

为什么只显示我的第一个子报表?

为什么 np.sum 只显示我的 np.array 的最后一个元素?

为什么我的嵌套gridview只显示一秒钟?

为什么我的 1 TB 硬盘上只显示 37.5 GB?

为什么在 PHP 中我的标记(Google 地图 api)上只显示一个变量?

为什么我的嵌套 ListViews 只显示外部模型的一个元素?

我的 JCheckBox 程序只显示一个框。这是为什么?

PyQt5 - 为什么我的标签只显示文件的最后一行?