2D数组到ImageData

时间:2018-02-07 15:24:48

标签: javascript html arrays image html5-canvas

所以我有一个像这样创建的2D数组:

//Fill screen as blank
for(var x = 0; x<500; x++ ){
    screen[x] = [];
    for(var y = 0; y<500; y++ ){
        screen[x][y] = '#ffffff';
    }
}

并且想知道是否有一种简单的方法将其转换为ImageData对象,以便我可以在画布上显示它?

2 个答案:

答案 0 :(得分:4)

展平数组

您需要学习的第一件事是如何展平二维数组。您可以使用嵌套循环并推送到新的1d数组,但我更喜欢使用reduceconcat

const concat = (xs, ys) => xs.concat(ys);

console.log(
  [[1,2,3],[4,5,6]].reduce(concat)
)

现在你会很快注意到你的矩阵会被翻转。 ImageData逐行连接,但您的矩阵按列分组(即[x][y]而不是[y][x])。我的建议是翻转你的嵌套循环:)

"#ffffff"[255, 255, 255, 255]

您现在可以使用该工具创建十六进制代码的一维数组(screen.reduce(concat)),但ImageData获取Uint8ClampedArray0-255值!我们来解决这个问题:

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

请注意,我跳过第一个"#"字符并将alpha值硬编码为255

从十六进制转换为RGBA

我们将使用map一次转换新创建的1d数组:

screen.reduce(concat).map(hexToRGBA);
再次

2d?!

回到原点......我们再次遇到一系列数组:

[ [255, 255, 255, 255], [255, 255, 255, 255], /* ... */ ]

但等等......我们已经知道如何解决这个问题了:

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

将数据放入canva

这是评论中链接的部分,但我会将其包含在内,以便您有一个有效的例子!

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

答案 1 :(得分:2)

我怀疑你的示例代码只是一个例子,但是以防万一没有更简单的方法来填充单一颜色的区域:

ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, 500, 500);

但回到扁平阵列。如果性能是一个因素,您可以通过以下方式进行:

旁注:如果可能的话 - 将颜色信息直接存储在相同的类型和字节顺序中,当你处理数十个字符串时,从字符串到数字的转换可能相对昂贵成千上万的像素 - 二进制/数字存储也更便宜。)

只需将2D数组直接展开/展平为类型化数组:

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

我们还需要将颜色的字符串表示法转换为little-endian顺序的数字(这些天大多数消费者CPU使用的格式)。 Shift,AND和OR操作比在字符串部分上工作快几倍,但如果你可以完全避免使用字符串,那么这将是理想的方法:

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

这里我们首先将字符串转换为数字 - 我们立即将其转换为Uint32值以优化编译器现在知道我们打算将以下转换中的数字用作整数。

由于我们最有可能在一个小端子平台上,我们必须在字节周围移位,掩码和OR以正确的字节顺序(即0xAABBGGRR)得到结果数字,并且在alpha通道中将OR作为不透明(在大的上) -endian平台你只需要将整个值左移8位,并在末尾的alpha通道中移动OR。

然后最后使用我们刚刚填充的基础ImageData创建一个ArrayBuffer对象,并为其提供Uint8ClampedArray ImageData所需的视图(这几乎没有任何开销,因为底层ArrayBuffer已共享):

var idata = new ImageData(new Uint8ClampedArray(data32.buffer), width, height);

从这里您可以使用context.putImageData(idata, x, y)

实施例

这里填充橙色以使转换可见(如果你得到的颜色不同于橙色,那么你就是在big-endian平台上:) :):

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>