【问题标题】:2D array to ImageData二维数组到 ImageData
【发布时间】:2018-07-17 22:55:02
【问题描述】:

所以我有一个像这样创建的二维数组:

//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 对象,以便我可以将其显示在画布上?

【问题讨论】:

标签: javascript html arrays image html5-canvas


【解决方案1】:

扁平化数组

您必须学习的第一件事是如何展平二维数组。您可以使用嵌套循环并推送到新的一维数组,但我更喜欢使用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 一次性转换新创建的一维数组:

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

将数据放入画布

这是在 cmets 中链接到的部分,但我会将其包含在内,以便您有一个工作示例!

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

【讨论】:

  • 另见 Array.flat()
【解决方案2】:

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

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

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

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

直接将二维数组展开/展平为类型化数组:

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)和 OR 在 alpha 通道中作为不透明的结果数字(在大-endian 平台,您只需将整个值左移 8 位,然后在最后的 alpha 通道中进行 OR)。

然后最后使用我们刚刚填充的底层ArrayBuffer 创建一个ImageData 对象,并给它一个Uint8ClampedArray 需要ImageData 的视图(这几乎没有开销,因为底层ArrayBuffer 是共享的):

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

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

示例

这里用橙色填充以使转换可见(如果您得到的颜色与橙色不同,那么您在大端平台上:)):

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);
&lt;canvas id=c width=500 height=500&gt;&lt;/canvas&gt;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-07-23
    • 2013-11-23
    • 1970-01-01
    • 1970-01-01
    • 2021-12-31
    • 2011-04-13
    • 2011-01-10
    • 2019-08-17
    相关资源
    最近更新 更多