首页 > 解决方案 > 在画布上创建纹理

问题描述

我想在画布上创建纹理。您可以选择 2 到 5 种颜色。对于每种颜色,您可以指定百分比。画布上的颜色分布应该是均匀的。

我将可能的颜色保存在一个数组中并随机选择索引并分配颜色。如果我选择一个大的混合比例(即 60% 和 40%),它似乎可以工作。但是如果比例变小(即90%/10%),结果就不行了。问题是在行尾只出现 90% 的颜色。对于像素操作,我使用 ImageData。我的想法是“逐行”操作。

请多多包涵,我是 JS 初学者,不会说英语母语这是我的代码:

  generateTexture(colors) {
  if (colors.length > 0) {
    let myColors = colors.slice();
    let canvas = document.getElementById('mycanvas');
    const w = canvas.getBoundingClientRect().width * 4;
    const h = canvas.getBoundingClientRect().height * 4;
    let context = canvas.getContext('2d');
    let i = 0;
    let sum = w * h;
    for (i = 0; i < myColors.length; i++) {
      myColors[i].sum = (sum / 100) * myColors[i].percentage;
      myColors[i].rowsum = myColors[i].sum / h;
    }

    let imageData = context.getImageData(0, 0, w, h);
    let data = imageData.data;
    let height = 0;
    while (height <= h) {
      for (i = 0; i < myColors.length; i++) {
        myColors[i].sum = myColors[i].rowsum;
      }
      let start = 0;
      start = height * h;
      let end = start + w * 4;
      let x = start;
      while (x < end) {
        let colIndex = 0;
        if (colors.length > 1) {
          colIndex = Math.floor(Math.random() * myColors.length);
        }
        if (myColors[colIndex].sum > 0) {
          let val = myColors[colIndex].color.split(',');
          let r = parseInt(val[0]);
          let g = parseInt(val[1]);
          let b = parseInt(val[2]);
          data[x] = r;
          data[x + 1] = g;
          data[x + 2] = b;
          data[x + 3] = 255;
          myColors[colIndex].sum = myColors[colIndex].sum - 1;
          x = x + 4;
        }
      }
      height++;
    }
    context.putImageData(imageData, 0, 0);
    canvas.style.webkitFilter = 'blur(.35px)';
  }
},

},

标签: javascriptcanvascolors

解决方案


您的功能有几个问题。

画布分辨率

canvas.getBoundingClientRect().width可能会或可能不会返回画布分辨率。它返回的是页面上的大小。使用canvas.widthcanvas.height获取画布的分辨率。

错误的高度值

您将高度乘以 4。因此,您要填充的像素通道 (RGBA) 的总数是w * 4 * h * 44 倍。它应该是w * h * 4

随机性

该函数generateTexture并不是真正随机的,因为您是从少数颜色中随机挑选的,这些颜色并不代表您所追求的真实分布与百分比。

假设你有 90% 的蓝色和 10% 的红色。

当您选择一种颜色时,您会随机选择蓝色或红色,50/50 的偶数几率,因此对于图像的前 20%,它将是 50% 的蓝色和 50% 的红色。然后你的红色用完了(用完 10%),除了蓝色你别无选择。

结果是随机分布的条带,直到最后只有一种颜色。

分布不良的例子

您的选择如何导致非常非随机纹理的示例。(带状)

function generateTexture(colors, ctx) {
    var i;
    const colSel = [...colors];
    const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
    const data32 = new Uint32Array(imgData.data.buffer);
    const pixels = data32.length;
    for (const col of colSel) {
        const rgb = col.color.split(',');
        col.RGBA32 = 0xFF000000 + ((rgb[2] & 0xFF) << 16) + ((rgb[1] & 0xFF) << 8) + (rgb[0] & 0xFF);
        col.pixels = Math.round(pixels * (col.percentage / 100));
    }
    i = 0;
    while (i < pixels) { 
        const idx = Math.random() * colSel.length | 0;
        const col = colSel[idx];
        data32[i++] = col.RGBA32;
        col.pixels --;
        if (col.pixels <= 0) {
            colSel.splice(idx, 1);
            if (colSel.length === 1) {
                const col = colSel[0];
                while (i < pixels) { data32[i++] = col.RGBA32 }
            }
        }
    }
    ctx.putImageData(imgData, 0, 0);

}




const colors = [];
const sum = 100;
var colCount = 0;
canvas.addEventListener("click", randomize);
function randomize(){
    colors.length = colCount = 0;
    colList.innerHTML = "";
    while(colCount < 100) {
        const col = randomColor();
        colors.push(col);
        $$(colList, $$($("li"),
            $("span", {textContent: col.percentage + "%"}),
            $("span", { className: "colBox", style: "background:rgb("+col.color+ ");"})
        ));
    }
    generateTexture(colors, canvas.getContext("2d"));
}
const randByte = () => Math.random() * 255 | 0;
function randomColor() {
    const remaining = sum - colCount;
    const percentage = remaining < 5 ? remaining  : (Math.random()** 0.33)* remaining | 0;
    colCount += percentage;
    return {
        color: [randByte(), randByte(), randByte()].join(","),
        percentage,
    }
}
const $$ = (el, ...sibs) => sibs.reduce((e,sib)=>(e.appendChild(sib), e), el);
const $ = (tag, p ={}) => Object.assign(document.createElement(tag),p);
randomize();
* {margin: 0px; font-family: arial}
canvas {
  position: absolute;
  top: opx;
  left: 0px;
  width: 100%;
  height: 100%;
  image-rendering: pixelated;
}
div {
  position: absolute;
  top: 20px;
  left: 20px;
  background: white;
  padding: 2px 4px;
  border: 1px solid black;
}
#colList {

}
.colBox {
   width: 64px;
   height: 1.2em;
   position: absolute;
   right: 4px;
}
<canvas id="canvas"  width="75" height="30"></canvas>
<div>Click for another example run
<ul id="colList"> </ul>
</div>

按百分比的随机项

您说这是纹理,那么实际像素的数量是否与所需的百分比匹配真的很重要。

如果您创建一个包含 100 种颜色的数组。对于 90% 的颜色,将其添加到数组中 90 次,对于 10% 的颜色,将其添加 10 次。

然后从该数组中随机选择,您将获得所需的分布。

真实随机分布示例

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

      for (const col of colors) {
          const rgb = col.color.split(',');
          const RGBA32 = 0xFF000000 + ((rgb[2] & 0xFF) << 16) + ((rgb[1] & 0xFF) << 8) + (rgb[0] & 0xFF);
          i = col.percentage;

          while (i-- > 0) { colSel.push(RGBA32) }
      }

      const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
      const data32 = new Uint32Array(imgData.data.buffer);
      i = len = data32.length;
      while (i-- > 0) { data32[i] =  colSel[Math.random() * 100 | 0] }
      ctx.putImageData(imgData, 0, 0);

}




const colors = [];
const sum = 100;
var colCount = 0;
canvas.addEventListener("click", randomize);
function randomize(){
    colors.length = colCount = 0;
    colList.innerHTML = "";
    while(colCount < 100) {
        const col = randomColor();
        colors.push(col);
        $$(colList, $$($("li"),
            $("span", {textContent: col.percentage + "%"}),
            $("span", { className: "colBox", style: "background:rgb("+col.color+ ");"})
        ));
    }
    generateTexture(colors, canvas.getContext("2d"));
}
const randByte = () => Math.random() * 255 | 0;
function randomColor() {
    const remaining = sum - colCount;
    const percentage = remaining < 5 ? remaining : (Math.random()** 0.33)* remaining | 0;
    colCount += percentage;
    return {
        color: [randByte(), randByte(), randByte()].join(","),
        percentage,
    }
}
const $$ = (el, ...sibs) => sibs.reduce((e,sib)=>(e.appendChild(sib), e), el);
const $ = (tag, p ={}) => Object.assign(document.createElement(tag),p);
randomize();
* {margin: 0px; font-family: arial}
canvas {
  position: absolute;
  top: opx;
  left: 0px;
  width: 100%;
  height: 100%;
  image-rendering: pixelated;
}
div {
  position: absolute;
  top: 20px;
  left: 20px;
  background: white;
  padding: 2px 4px;
  border: 1px solid black;
}
#colList {

}
.colBox {
   width: 64px;
   height: 1.2em;
   position: absolute;
   right: 4px;
}
<canvas id="canvas"  width="75" height="30"></canvas>
<div>Click for random texture
<ul id="colList"> </ul>
</div>

随机播放像素

如果图像中每种颜色的正确像素数非常重要,那么您可以使用随机随机播放。

将颜色 1 x 1 添加到图像中,以达到所需的确切计数。

然后随机打乱像素。

示例随机洗牌

function generateTexture(colors, ctx) {
    var i, idx = 0, RGBA32;
    const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
    const data32 = new Uint32Array(imgData.data.buffer);
    const pixels = data32.length;
    for (const col of colors) {
        const rgb = col.color.split(',');
        RGBA32 = 0xFF000000 + ((rgb[2] & 0xFF) << 16) + ((rgb[1] & 0xFF) << 8) + (rgb[0] & 0xFF);
        
        i = Math.round((col.percentage / 100) * pixels);
        while(i-- >= 0) { data32[idx++] = RGBA32 }
    }
    // fill any remaining 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.putImageData(imgData, 0, 0);

}




const colors = [];
const sum = 100;
var colCount = 0;
canvas.addEventListener("click", randomize);
function randomize(){
    colors.length = colCount = 0;
    colList.innerHTML = "";
    while(colCount < 100) {
        const col = randomColor();
        colors.push(col);
        $$(colList, $$($("li"),
            $("span", {textContent: col.percentage + "%"}),
            $("span", { className: "colBox", style: "background:rgb("+col.color+ ");"})
        ));
    }
    generateTexture(colors, canvas.getContext("2d"));
}
const randByte = () => Math.random() * 255 | 0;
function randomColor() {
    const remaining = sum - colCount;
    const percentage = remaining < 5 ? remaining : (Math.random()** 0.33)* remaining | 0;
    colCount += percentage;
    return {
        color: [randByte(), randByte(), randByte()].join(","),
        percentage,
    }
}
const $$ = (el, ...sibs) => sibs.reduce((e,sib)=>(e.appendChild(sib), e), el);
const $ = (tag, p ={}) => Object.assign(document.createElement(tag),p);
randomize();
* {margin: 0px; font-family: arial}
canvas {
  position: absolute;
  top: opx;
  left: 0px;
  width: 100%;
  height: 100%;
  image-rendering: pixelated;
}
div {
  position: absolute;
  top: 20px;
  left: 20px;
  background: white;
  padding: 2px 4px;
  border: 1px solid black;
}
#colList {

}
.colBox {
   width: 64px;
   height: 1.2em;
   position: absolute;
   right: 4px;
}
<canvas id="canvas" width="75" height="30"></canvas>
<div>Click for random texture
<ul id="colList"> </ul>
</div>


推荐阅读