Перемешать на месте
В некоторых ответах предлагается использовать алгоритм Фишера Йейтса для создания случайного распределения пикселей, что является наилучшим способом получения случайного и равномерного распределения фиксированного набора значений (пикселей в этом случае)
Однако оба ответа создали очень плохие реализации, которые дублируют пиксели (используя больше оперативной памяти, чем необходимо, и, следовательно, дополнительные циклы ЦП), и оба обрабатывают пиксели на канал, а не как дискретные элементы (пережевывая больше циклов ЦП)
Используйте данные изображения, полученные из 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
dr aws 1 пиксель на холсте для каждого цвета, а затем использует данные пикселей в качестве Uint32 для установки цветового соотношения и перетасовывает массив пикселей, используя стандартный алгоритм перемешивания (Фишер Йейтс)
Используйте ползунок для изменения цветового соотношения.
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;
См. Альнитак отличный ответ или альтернативный демонстрационный вариант того же подхода в следующем фрагменте.
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" />