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

时间:2020-01-31 13:09:02

标签: javascript arrays canvas pixel

我想使用JavaScript创建包含两种颜色(例如橙色和蓝色)的128x128像素的图像。像素应该随机排列,我希望能够改变两种颜色的比例(例如,像“ 0.4橙色”这样的状态可以得到40%的橙色和60%的蓝色像素)。

应该看起来像这样:

it should look somewhat like this

我在这里找到一个脚本来创建具有随机像素颜色的画布,并将其修改为仅给我橙色的画布。我基本上在挣扎的是创建将颜色值分配给像素的“ 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);

4 个答案:

答案 0 :(得分:3)

就地随机播放

一些答案​​建议使用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 excellent answer或下一个代码段中使用相同方法的另一种演示变体。

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" />

答案 1 :(得分:2)

您可以为画布上的像素数创建一个颜色数组。对于每个像素,您可以指定一个rgba数组,该数组保存该像素应采用的rgba值。最初,您可以使用40%的橙色rgba阵列和60%的蓝色rgba阵列填充颜色阵列。然后,您可以将此数组混排以获取随机的颜色分布。最后,您可以遍历每个像素并使用颜色数组中与像素相关的值来设置其颜色。

请参见以下示例:

const canvas = document.createElement('canvas');
canvas.width = canvas.height = 128;
const ctx = canvas.getContext('2d');
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
document.body.appendChild(canvas);

// From: https://stackoverflow.com/a/6274381/5648954
function shuffle(a) {
  for (let i = a.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [a[i], a[j]] = [a[j], a[i]];
  }
  return a;
}

function generateColors() {
  const randomColorsArr = [];
  const colors = {
    orange: [255, 128, 0, 255],
    blue: [0, 0, 255, 255]
  }
  // For each pixel add an rgba array to randomColorsArr
  for (let i = 0; i < imgData.data.length/4; i++) {
    if (i/(imgData.data.length/4) < 0.4)
      randomColorsArr.push(colors.orange); // add orange to fill up the first 40% of the array
    else
      randomColorsArr.push(colors.blue); // add blue to fill up the remaining 60% of the array
  }
  return shuffle(randomColorsArr); // shuffle the colors around
}

// For each pixel, get and set the color
function displayPx() {
  const randomColorsArr = generateColors();
  for (let i = 0; i < randomColorsArr.length; i++) {
    let rgba = randomColorsArr[i];
    for (let j = 0; j < rgba.length; j++)
      imgData.data[i*4 + j] = rgba[j];
  }
  ctx.putImageData(imgData, 0, 0);
}

displayPx();

答案 2 :(得分:2)

如果要进行粗略分布,则可以在if条件中简单地使用Math.random(),如下所示:

if (Math.random() <= percentage){
  setPixel(color1);
} else {
  setPixel(color2);
}

这使您在画布中平均获得{%}的橙色像素。每次创建画布时,确切的数量都不同。画布越大,您越接近实际百分比,如您期望的那样,数字越大。

如果您希望每次进行精确的分配都会有点乏味:

  1. 创建一个大小为#“ pixelAmount”的数组
  2. 用精确的1和0填充数组
  3. 随机随机排列数组(我使用了fisherYates算法)

可能还有其他方法可以实现这一目标,但是我发现这很简单。

我添加了一些幻想来尝试百分比;-)

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);
let pixelAmount = canvas.width * canvas.height;
let colorArray = new Array(pixelAmount - 1);

// The values you want
let percentage;
let color1 = [255, 128, 0, 255];
let color2 = [0, 0, 255, 255];

document.body.appendChild(canvas);


function colorize() {
  // this just checks the amount of pixels colored with color1
  let testColor1 = 0;
  for (var i = 0; i < imgData.data.length; i += 4) {
    if (colorArray[i/4]) {
      setPixelColor(i, color1);
      testColor1++;
    } else {
      setPixelColor(i, color2);
    }
  }
  console.clear();
  console.log(`pixels:${pixelAmount} / color1:${testColor1} / percentage:${Math.round(testColor1/pixelAmount*100)}%`);
  ctx.putImageData(imgData, 0, 0);
}


function setPixelColor(pixel, color) {
  imgData.data[pixel] = color[0]; // red
  imgData.data[pixel + 1] = color[1]; // green
  imgData.data[pixel + 2] = color[2]; // blue
  imgData.data[pixel + 3] = color[3]; // alpha
}




function fisherYates(array) {
  var i = array.length;
  if (i == 0) return false;
  while (--i) {
    var j = Math.floor(Math.random() * (i + 1));
    var tempi = array[i];
    var tempj = array[j];
    array[i] = tempj;
    array[j] = tempi;
  }
}

let input = document.querySelector("input");
input.addEventListener("change", recalculate);
document.querySelector("button").addEventListener("click",recalculate);

function recalculate() {
  percentage = input.value;
  console.log("triggered:"+percentage);
  colorPixels = Math.round(pixelAmount * percentage);
  colorArray.fill(1, 0, colorPixels);
  colorArray.fill(0, colorPixels + 1);
  fisherYates(colorArray);
  colorize();
}
recalculate();
input {
  width: 50px;
}
<input type="number" min="0" max="1" step="0.05" value="0.05"> 
<button>new</button>
<br>

答案 3 :(得分:2)

只是为了好玩,这是一个版本(使用不准确的概率),尽管它通过使用32位ABGR常量可能会带来可移植性的代价,但它的效率可能尽可能地高:

const canvas = document.createElement('canvas');
canvas.width = canvas.height = 128;
const ctx = c.getContext('2d');
const imgData = ctx.createImageData(c.width, c.height);
const data = imgData.data;
const buf = data.buffer;
const u32 = new Uint32Array(buf);

for (let i = 0; i < u32.length; ++i) {
    u32[i] = Math.random() < 0.4 ? 0xff0080ff : 0xffff0000;
}    
ctx.putImageData(imgData, 0, 0);

在OP的128x128分辨率下,它可以每帧一次刷新整个画布,并保留CPU负载。演示在https://jsfiddle.net/alnitak/kbL9y0j5/18/

相关问题