所以我有一个像这样创建的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对象,以便我可以在画布上显示它?
答案 0 :(得分:4)
您需要学习的第一件事是如何展平二维数组。您可以使用嵌套循环并推送到新的1d数组,但我更喜欢使用reduce
和concat
:
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
获取Uint8ClampedArray
个0-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
。
我们将使用map
一次转换新创建的1d数组:
screen.reduce(concat).map(hexToRGBA);
再次回到原点......我们再次遇到一系列数组:
[ [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
这是评论中链接的部分,但我会将其包含在内,以便您有一个有效的例子!
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>