我想使用JavaScript创建包含两种颜色(例如橙色和蓝色)的128x128像素的图像。像素应该随机排列,我希望能够改变两种颜色的比例(例如,像“ 0.4橙色”这样的状态可以得到40%的橙色和60%的蓝色像素)。
它看起来应该像这样:
我在这里找到一个脚本来创建具有随机像素颜色的画布,并将其修改为仅给我橙色的画布。我基本上在挣扎的是创建将颜色值分配给像素的“ if语句”。我虽然要创建一个数组,使用propotion_orange * 128 * 128唯一的元素在1和128 * 128之间随机绘制。然后针对每个像素值检查该像素值是否在该数组中,如果是,则为其分配橙色,否则分配为蓝色。对JS来说是全新的,我在创建这样的数组时遇到了麻烦。我希望我能够以一种可以理解的方式陈述我的问题。
那是我发现的用于随机像素颜色的脚本,我将其修改为仅给我橙色:
var canvas = document.createElement('canvas');
canvas.width = canvas.height = 128;
var ctx = canvas.getContext('2d');
var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
for (var i = 0; i < imgData.data.length; i += 4) {
imgData.data[i] = 255; // red
imgData.data[i + 1] = 128; // green
imgData.data[i + 2] = 0; // blue
imgData.data[i + 3] = 255; // alpha
}
ctx.putImageData(imgData, 0, 0);
document.body.appendChild(canvas);
一些答案建议使用费舍尔·耶茨(Fisher Yates)算法创建像素的随机分布,这是迄今为止获得一组固定值(在这种情况下为像素)的随机且均匀分布的最佳方法
但是,这两个答案都产生了非常差的实现,它们会重复像素(使用比所需数量更多的RAM,从而导致额外的CPU周期),并且都处理每个通道的像素,而不是谨慎处理(花费更多的CPU周期)
使用从中获取的图像数据ctx.getImageData
来保持阵列的随机播放。它还提供了一种从CSS颜色值转换为像素数据的便捷方法。
以下随机播放功能将混合所有保留所有颜色的画布。使用32位类型的数组,您可以一次操作移动整个像素。
function shuffleCanvas(ctx) {
const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
const p32 = new Uint32Array(imgData.data.buffer); // get 32 bit pixel data
var i = p32.length, idx;
while (i--) {
const b = p32[i];
p32[i] = p32[idx = Math.random() * (i + 1) | 0];
p32[idx] = b;
}
ctx.putImageData(imgData, 0, 0); // put shuffled pixels back to canvas
}
该演示添加了一些功能。该函数fillRatioAndShffle
为每种颜色在画布上绘制1个像素,然后使用像素数据作为Uint32来设置颜色比率,并使用标准的混洗算法(Fisher Yates)对像素数组进行混洗
使用滑块更改色彩比例。
const ctx = canvas.getContext("2d");
var currentRatio;
fillRatioAndShffle(ctx, "black", "red", 0.4);
ratioSlide.addEventListener("input", () => {
const ratio = ratioSlide.value / 100;
if (ratio !== currentRatio) { fillRatioAndShffle(ctx, "black", "red", ratio) }
});
function fillRatioAndShffle(ctx, colA, colB, ratio) {
currentRatio = ratio;
ctx.fillStyle = colA;
ctx.fillRect(0, 0, 1, 1);
ctx.fillStyle = colB;
ctx.fillRect(1, 0, 1, 1);
const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
const p32 = new Uint32Array(imgData.data.buffer); // get 32 bit pixel data
const pA = p32[0], pB = p32[1]; // get colors
const pixelsA = p32.length * ratio | 0;
p32.fill(pA, 0, pixelsA);
p32.fill(pB, pixelsA);
!(ratio === 0 || ratio === 1) && shuffle(p32);
ctx.putImageData(imgData, 0, 0);
}
function shuffle(p32) {
var i = p32.length, idx, t;
while (i--) {
t = p32[i];
p32[i] = p32[idx = Math.random() * (i + 1) | 0];
p32[idx] = t;
}
}
<canvas id="canvas" width="128" height="128"></canvas>
<input id="ratioSlide" type="range" min="0" max="100" value="40" />
?
而不是if
没有人会注意到这种混合是有点过度还是有点不足。更好的方法是按几率混合(如果随机值低于固定几率)。
对于两个值,三元是最优雅的解决方案
pixel32[i] = Math.random() < 0.4 ? 0xFF0000FF : 0xFFFF0000;
在下一个代码片段中,请参阅Alnitak 最佳答案或相同方法的替代演示变体。
const ctx = canvas.getContext("2d");
var currentRatio;
const cA = 0xFF000000, cB = 0xFF00FFFF; // black and yellow
fillRatioAndShffle(ctx, cA, cB, 0.4);
ratioSlide.addEventListener("input", () => {
const ratio = ratioSlide.value / 100;
if (ratio !== currentRatio) { fillRatioAndShffle(ctx, cA, cB, ratio) }
currentRatio = ratio;
});
function fillRatioAndShffle(ctx, cA, cB, ratio) {
const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
const p32 = new Uint32Array(imgData.data.buffer); // get 32 bit pixel data
var i = p32.length;
while (i--) { p32[i] = Math.random() < ratio ? cA : cB }
ctx.putImageData(imgData, 0, 0);
}
<canvas id="canvas" width="128" height="128"></canvas>
<input id="ratioSlide" type="range" min="0" max="100" value="40" />