创建具有不同颜色比例的双色 canvas
creating a bi-colored canvas with varying color proportions
我想使用 javascript 创建一个包含两种颜色(例如橙色和蓝色)的 128x128 像素的图像。像素应随机排列,我希望能够改变两种颜色的比例(例如,状态为“0.4 橙色”以获得 40% 橙色和 60% 蓝色像素)。
它应该看起来像这样:
我在这里找到了一个脚本来创建一个 canvas 具有随机像素颜色并修改它只给我橙色的。我基本上正在努力的是创建 "if statement" 将颜色值分配给像素。我想创建一个数组,其中 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);
您可以为 canvas 上的像素数创建一个颜色数组。对于每个像素,您可以指定一个 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:
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();
如果你想要一个粗略的分配,你可以简单地在 if 条件中使用 Math.random()
像这样:
if (Math.random() <= percentage){
setPixel(color1);
} else {
setPixel(color2);
}
这为您提供了 canvas 中橙色像素的平均 {percentage} 数量。每次创建 canvas 时,确切的数量都不同。 canvas 越大,您就越接近实际百分比,正如您对大数字的概率所期望的那样。
如果你想要每次都精确分配有点乏味:
- 创建一个大小为 #"pixelAmount"
的数组
- 用 1 和 0 的确切数量填充数组
- 随机打乱数组(我用的是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>
只是为了好玩,这是一个可能尽可能高效的版本(使用不精确的概率),尽管通过使用 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 分辨率下,这可以简单地每帧刷新整个 canvas 一次,并留下 CPU 的负载。在 https://jsfiddle.net/alnitak/kbL9y0j5/18/
进行演示
洗牌到位
一些答案建议使用 Fisher Yates 算法来创建像素的随机分布,这是迄今为止获得一组固定值(在本例中为像素)的随机均匀分布的最佳方法
然而,这两个答案都创建了非常糟糕的实现,复制像素(使用比需要更多的 RAM,因此需要额外的 CPU 周期)并且都处理每个通道的像素而不是作为谨慎的项目(咀嚼更多 CPU 周期)
使用您从ctx.getImageData
获得的图像数据来保存要随机播放的数组。它还提供了一种将 CSS 颜色值转换为像素数据的便捷方法。
随机播放现有图片
以下随机播放函数将混合任何 canvas 并保留所有颜色。使用 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 个像素到 canvas,然后使用像素数据作为 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" />
我想使用 javascript 创建一个包含两种颜色(例如橙色和蓝色)的 128x128 像素的图像。像素应随机排列,我希望能够改变两种颜色的比例(例如,状态为“0.4 橙色”以获得 40% 橙色和 60% 蓝色像素)。
它应该看起来像这样:
我在这里找到了一个脚本来创建一个 canvas 具有随机像素颜色并修改它只给我橙色的。我基本上正在努力的是创建 "if statement" 将颜色值分配给像素。我想创建一个数组,其中 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);
您可以为 canvas 上的像素数创建一个颜色数组。对于每个像素,您可以指定一个 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:
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();
如果你想要一个粗略的分配,你可以简单地在 if 条件中使用 Math.random()
像这样:
if (Math.random() <= percentage){
setPixel(color1);
} else {
setPixel(color2);
}
这为您提供了 canvas 中橙色像素的平均 {percentage} 数量。每次创建 canvas 时,确切的数量都不同。 canvas 越大,您就越接近实际百分比,正如您对大数字的概率所期望的那样。
如果你想要每次都精确分配有点乏味:
- 创建一个大小为 #"pixelAmount" 的数组
- 用 1 和 0 的确切数量填充数组
- 随机打乱数组(我用的是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>
只是为了好玩,这是一个可能尽可能高效的版本(使用不精确的概率),尽管通过使用 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 分辨率下,这可以简单地每帧刷新整个 canvas 一次,并留下 CPU 的负载。在 https://jsfiddle.net/alnitak/kbL9y0j5/18/
进行演示洗牌到位
一些答案建议使用 Fisher Yates 算法来创建像素的随机分布,这是迄今为止获得一组固定值(在本例中为像素)的随机均匀分布的最佳方法
然而,这两个答案都创建了非常糟糕的实现,复制像素(使用比需要更多的 RAM,因此需要额外的 CPU 周期)并且都处理每个通道的像素而不是作为谨慎的项目(咀嚼更多 CPU 周期)
使用您从ctx.getImageData
获得的图像数据来保存要随机播放的数组。它还提供了一种将 CSS 颜色值转换为像素数据的便捷方法。
随机播放现有图片
以下随机播放函数将混合任何 canvas 并保留所有颜色。使用 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 个像素到 canvas,然后使用像素数据作为 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" />