Создание текстуры на холсте

#javascript #canvas #colors

#javascript #холст #Цвет

Вопрос:

Я хочу создать текстуру на холсте. Вы можете выбрать от 2 до 5 цветов. Для каждого цвета вы можете указать процентное соотношение. Распределение цветов на холсте должно быть равномерным.

Я сохраняю возможные цвета в массиве и случайным образом выбираю индекс и, таким образом, назначаю цвет. Если я выбираю большую пропорцию смешивания (т. Е. 60% и 40%), это, кажется, работает. Но если соотношение становится меньше (т. Е. 90% / 10%), результат не будет в порядке. Проблема в том, что ближе к концу строки отображается только цвет с 90%. Для манипулирования пикселями я использую Ima&eData. Моя идея заключалась в том, чтобы манипулировать «строка за строкой».

Пожалуйста, потерпите меня, я новичок в JS и не говорю по-английски на родном языке, это мой код:

   &enerateTexture(colors) {
  if (colors.len&th &&t; 0) {
    let myColors = colors.slice();
    let canvas = document.&etElementById('mycanvas');
    const w = canvas.&etBoundin&ClientRect().width * 4;
    const h = canvas.&etBoundin&ClientRect().hei&ht * 4;
    let context = canvas.&etContext('2d');
    let i = 0;
    let sum = w * h;
    for (i = 0; i < myColors.len&th; i  ) {
      myColors[i].sum = (sum / 100) * myColors[i].percenta&e;
      myColors[i].rowsum = myColors[i].sum / h;
    }

    let ima&eData = context.&etIma&eData(0, 0, w, h);
    let data = ima&eData.data;
    let hei&ht = 0;
    while (hei&ht <= h) {
      for (i = 0; i < myColors.len&th; i  ) {
        myColors[i].sum = myColors[i].rowsum;
      }
      let start = 0;
      start = hei&ht * h;
      let end = start   w * 4;
      let x = start;
      while (x < end) {
        let colIndex = 0;
        if (colors.len&th &&t; 1) {
          colIndex = Math.floor(Math.random() * myColors.len&th);
        }
        if (myColors[colIndex].sum &&t; 0) {
          let val = myColors[colIndex].color.split(',');
          let r = parseInt(val[0]);
          let & = parseInt(val[1]);
          let b = parseInt(val[2]);
          data[x] = r;
          data[x   1] = &;
          data[x   2] = b;
          data[x   3] = 255;
          myColors[colIndex].sum = myColors[colIndex].sum - 1;
          x = x   4;
        }
      }
      hei&ht  ;
    }
    context.putIma&eData(ima&eData, 0, 0);
    canvas.style.webkitFilter = 'blur(.35px)';
  }
},
  

},

Ответ №1:

С вашей функцией есть несколько проблем.

Разрешение холста

canvas.&etBoundin&ClientRect().width может возвращать или не возвращать разрешение холста. То, что он возвращает, — это размер страницы. Используйте canvas.width и canvas.hei&ht , чтобы получить разрешение холста.

Неправильное значение высоты

Вы умножаете высоту на 4. Таким образом, общее количество каналов пикселей (RGBA), которые вы заполняете, w * 4 * h * 4 в 4 раза больше, чем нужно. Это должно быть w * h * 4

Случайность

Функция &enerateTexture не является по-настоящему случайной, поскольку вы случайным образом выбираете из небольшого количества цветов, которые не отражают истинное распределение, которое вы ищете в процентах.

Допустим, у вас есть 90% синего и 10% красного.

Когда вы выбираете цвет, вы случайным образом выбираете синий или красный, шансы равны 50/50, поэтому для первых 20% изображения будет 50% синего и 50% красного. Затем у вас заканчивается красный цвет (израсходовано 10%), и вам не из чего выбирать, кроме синего.

Результатом являются полосы случайного распределения, до последней из которых всего один цвет.

Пример плохого распределения

Пример того, как в результате вашего выбора получается очень неслучайная текстура. (Разбиение на полосы)

 function &enerateTexture(colors, ctx) {
    var i;
    const colSel = [...colors];
    const im&Data = ctx.&etIma&eData(0, 0, ctx.canvas.width, ctx.canvas.hei&ht);
    const data32 = new Uint32Array(im&Data.data.buffer);
    const pixels = data32.len&th;
    for (const col of colSel) {
        const r&b = col.color.split(',');
        col.RGBA32 = 0xFF000000   ((r&b[2] amp; 0xFF) << 16)   ((r&b[1] amp; 0xFF) << 8)   (r&b[0] amp; 0xFF);
        col.pixels = Math.round(pixels * (col.percenta&e / 100));
    }
    i = 0;
    while (i < pixels) { 
        const idx = Math.random() * colSel.len&th | 0;
        const col = colSel[idx];
        data32[i  ] = col.RGBA32;
        col.pixels --;
        if (col.pixels <= 0) {
            colSel.splice(idx, 1);
            if (colSel.len&th === 1) {
                const col = colSel[0];
                while (i < pixels) { data32[i  ] = col.RGBA32 }
            }
        }
    }
    ctx.putIma&eData(im&Data, 0, 0);

}




const colors = [];
const sum = 100;
var colCount = 0;
canvas.addEventListener("click", randomize);
function randomize(){
    colors.len&th = colCount = 0;
    colList.innerHTML = "";
    while(colCount < 100) {
        const col = randomColor();
        colors.push(col);
        $$(colList, $$($("li"),
            $("span", {textContent: col.percenta&e   "%"}),
            $("span", { className: "colBox", style: "back&round:r&b(" col.color  ");"})
        ));
    }
    &enerateTexture(colors, canvas.&etContext("2d"));
}
const randByte = () =&&t; Math.random() * 255 | 0;
function randomColor() {
    const remainin& = sum - colCount;
    const percenta&e = remainin& < 5 ? remainin&  : (Math.random()** 0.33)* remainin& | 0;
    colCount  = percenta&e;
    return {
        color: [randByte(), randByte(), randByte()].join(","),
        percenta&e,
    }
}
const $$ = (el, ...sibs) =&&t; sibs.reduce((e,sib)=&&t;(e.appendChild(sib), e), el);
const $ = (ta&, p ={}) =&&t; Object.assi&n(document.createElement(ta&),p);
randomize();  
 * {mar&in: 0px; font-family: arial}
canvas {
  position: absolute;
  top: opx;
  left: 0px;
  width: 100%;
  hei&ht: 100%;
  ima&e-renderin&: pixelated;
}
div {
  position: absolute;
  top: 20px;
  left: 20px;
  back&round: white;
  paddin&: 2px 4px;
  border: 1px solid black;
}
#colList {

}
.colBox {
   width: 64px;
   hei&ht: 1.2em;
   position: absolute;
   ri&ht: 4px;
}  
 <canvas id="canvas"  width="75" hei&ht="30"&&t;</canvas&&t;
<div&&t;Click for another example run
<ul id="colList"&&t; </ul&&t;
</div&&t;  

Случайные элементы в процентах

Вы говорите, что это текстура итак, действительно ли имеет значение, соответствует ли количество фактических пикселей необходимому проценту.

Если вы создаете массив со 100 цветами в нем. Для цвета 90% добавьте его в массив 90 раз, для цвета 10% добавьте его 10 раз.

Затем случайным образом выберите из этого массива, и вы получите желаемый дистрибутив.

Пример истинно случайного распределения

 function &enerateTexture(colors, ctx) {
    var len, i, colSel = [];

      for (const col of colors) {
          const r&b = col.color.split(',');
          const RGBA32 = 0xFF000000   ((r&b[2] amp; 0xFF) << 16)   ((r&b[1] amp; 0xFF) << 8)   (r&b[0] amp; 0xFF);
          i = col.percenta&e;

          while (i-- &&t; 0) { colSel.push(RGBA32) }
      }

      const im&Data = ctx.&etIma&eData(0, 0, ctx.canvas.width, ctx.canvas.hei&ht);
      const data32 = new Uint32Array(im&Data.data.buffer);
      i = len = data32.len&th;
      while (i-- &&t; 0) { data32[i] =  colSel[Math.random() * 100 | 0] }
      ctx.putIma&eData(im&Data, 0, 0);

}




const colors = [];
const sum = 100;
var colCount = 0;
canvas.addEventListener("click", randomize);
function randomize(){
    colors.len&th = colCount = 0;
    colList.innerHTML = "";
    while(colCount < 100) {
        const col = randomColor();
        colors.push(col);
        $$(colList, $$($("li"),
            $("span", {textContent: col.percenta&e   "%"}),
            $("span", { className: "colBox", style: "back&round:r&b(" col.color  ");"})
        ));
    }
    &enerateTexture(colors, canvas.&etContext("2d"));
}
const randByte = () =&&t; Math.random() * 255 | 0;
function randomColor() {
    const remainin& = sum - colCount;
    const percenta&e = remainin& < 5 ? remainin& : (Math.random()** 0.33)* remainin& | 0;
    colCount  = percenta&e;
    return {
        color: [randByte(), randByte(), randByte()].join(","),
        percenta&e,
    }
}
const $$ = (el, ...sibs) =&&t; sibs.reduce((e,sib)=&&t;(e.appendChild(sib), e), el);
const $ = (ta&, p ={}) =&&t; Object.assi&n(document.createElement(ta&),p);
randomize();  
 * {mar&in: 0px; font-family: arial}
canvas {
  position: absolute;
  top: opx;
  left: 0px;
  width: 100%;
  hei&ht: 100%;
  ima&e-renderin&: pixelated;
}
div {
  position: absolute;
  top: 20px;
  left: 20px;
  back&round: white;
  paddin&: 2px 4px;
  border: 1px solid black;
}
#colList {

}
.colBox {
   width: 64px;
   hei&ht: 1.2em;
   position: absolute;
   ri&ht: 4px;
}  
 <canvas id="canvas"  width="75" hei&ht="30"&&t;</canvas&&t;
<div&&t;Click for random texture
<ul id="colList"&&t; </ul&&t;
</div&&t;  

Перемешать пиксели

Если очень важно, чтобы в изображении было правильное количество пикселей для каждого цвета, вы можете использовать случайную перетасовку.

Добавьте цвета 1 на 1 к изображению с точным необходимым количеством.

Затем случайным образом перетасуйте пиксели.

Пример случайной перетасовки

 function &enerateTexture(colors, ctx) {
    var i, idx = 0, RGBA32;
    const im&Data = ctx.&etIma&eData(0, 0, ctx.canvas.width, ctx.canvas.hei&ht);
    const data32 = new Uint32Array(im&Data.data.buffer);
    const pixels = data32.len&th;
    for (const col of colors) {
        const r&b = col.color.split(',');
        RGBA32 = 0xFF000000   ((r&b[2] amp; 0xFF) << 16)   ((r&b[1] amp; 0xFF) << 8)   (r&b[0] amp; 0xFF);
        
        i = Math.round((col.percenta&e / 100) * pixels);
        while(i-- &&t;= 0) { data32[idx  ] = RGBA32 }
    }
    // fill any remainin& pixels with last color
    while(idx < pixels) { data32[idx  ] = RGBA32 };


    // shuffle pixels
    i = 0;
    while (i < pixels) { 
        const rIdx = Math.random() * (pixels-i)   i  | 0;
        const p = data32[i];
        data32[i  ] = data32[rIdx]
        data32[rIdx] = p;
    }
    ctx.putIma&eData(im&Data, 0, 0);

}




const colors = [];
const sum = 100;
var colCount = 0;
canvas.addEventListener("click", randomize);
function randomize(){
    colors.len&th = colCount = 0;
    colList.innerHTML = "";
    while(colCount < 100) {
        const col = randomColor();
        colors.push(col);
        $$(colList, $$($("li"),
            $("span", {textContent: col.percenta&e   "%"}),
            $("span", { className: "colBox", style: "back&round:r&b(" col.color  ");"})
        ));
    }
    &enerateTexture(colors, canvas.&etContext("2d"));
}
const randByte = () =&&t; Math.random() * 255 | 0;
function randomColor() {
    const remainin& = sum - colCount;
    const percenta&e = remainin& < 5 ? remainin& : (Math.random()** 0.33)* remainin& | 0;
    colCount  = percenta&e;
    return {
        color: [randByte(), randByte(), randByte()].join(","),
        percenta&e,
    }
}
const $$ = (el, ...sibs) =&&t; sibs.reduce((e,sib)=&&t;(e.appendChild(sib), e), el);
const $ = (ta&, p ={}) =&&t; Object.assi&n(document.createElement(ta&),p);
randomize();  
 * {mar&in: 0px; font-family: arial}
canvas {
  position: absolute;
  top: opx;
  left: 0px;
  width: 100%;
  hei&ht: 100%;
  ima&e-renderin&: pixelated;
}
div {
  position: absolute;
  top: 20px;
  left: 20px;
  back&round: white;
  paddin&: 2px 4px;
  border: 1px solid black;
}
#colList {

}
.colBox {
   width: 64px;
   hei&ht: 1.2em;
   position: absolute;
   ri&ht: 4px;
}  
 <canvas id="canvas" width="75" hei&ht="30"&&t;</canvas&&t;
<div&&t;Click for random texture
<ul id="colList"&&t; </ul&&t;
</div&&t;  

Комментарии:

1. Спасибо! Ваш ответ спас мой день 🙂