温馨提示:本文翻译自stackoverflow.com,查看原文请点击:javascript - creating a bi-colored canvas with varying color proportions
arrays canvas javascript pixel

javascript - 创建具有不同颜色比例的双色画布

发布于 2020-04-06 23:38:54

我想使用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);

查看更多

提问者
David
被浏览
66
Blindman67 2020-02-01 19:18

洗牌到位

一些答案建议使用费舍尔·耶茨(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" />