मैं एक स्क्रिप्ट बना रहा हूं जो ग्रहों को स्वतः उत्पन्न करता है उदाहरण के लिए कोडपेन देखें। लेकिन मेरे पास समस्या यह है कि मैं इसे कम पिक्सेलयुक्त बनाना चाहता हूं और मुझे ऐसा करने में कुछ समस्या हो रही है कि अगर मैं टाइलों को 70 * 70 और टाइल का आकार 10 * 10 पिक्सेल कर दूं तो यह ठीक काम करता है। लेकिन मैं इसे टाइल 360 * 360 और आकार 1 या 2 पिक्सेल जैसी किसी चीज़ पर सेट करना चाहता हूं। लेकिन जब मैं ऐसा करने की कोशिश करता हूं तो मुझे अधिकतम कॉल स्टैक त्रुटि मिलती है। इसलिए मैंने requestAnimationFrame का उपयोग करने की कोशिश की, लेकिन फिर लोड होने में उम्र लग जाती है, क्या प्रक्रिया को तेज करने का कोई तरीका है?

var tileNum = 0;
    var tiles;
    var colorsLand;
    var colorsWater;
    var size = 360;
    var tileSize = 2;
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    window.onload = function () {
        generatePlanet();
    }

    function generatePlanet() {
        tileNum = 0;
        tiles = [{ x: 0, y: 0, land: false }];

        //Retrive colors        

        colorsLand = interpolateColors("rgb(" + getColor(true) + ")", "rgb(" + getColor(true) + ")", 6000);
        colorsWater = interpolateColors("rgb(" + getColor(false) + ")", "rgb(" + getColor(false) + ")", 6000);


        //Creates a array of my tiles and sets either water or land to them and calculates the % of being water/land
        for (var i = 0; i < (size * size); i++) {
            var currentTile = tiles[tiles.length - 1];
            if (currentTile.x <= (size - 1)) {
                var isLand = false;
                if (currentTile.land == true || tiles.length > size && tiles[tiles.length - size].land == true) {
                    isLand = (Math.floor(Math.random() * 100) + 1) > 35;
                }
                else if (currentTile.land == true || tiles.length > size &&
                    (tiles[tiles.length - 1].land == true ||
                        tiles[tiles.length - size].land == true)) {
                    isLand = (Math.floor(Math.random() * 100) + 1) > size;
                }
                else {
                    isLand = (Math.floor(Math.random() * 100) + 1) > 99;
                }
                tiles.push({ x: currentTile.x + 1, y: currentTile.y, land: isLand });
            }
            else {
                tiles.push({ x: 0, y: currentTile.y + 1, land: isLand });
            }
        }
        drawPlanet()
    }


    //retrive a random color if it's a land tile i want it dark water i want light
    function getColor(land) {
        while (true) {
            var r = Math.floor(Math.random() * 256) + 1
            var g = Math.floor(Math.random() * 256) + 1
            var b = Math.floor(Math.random() * 256) + 1
            var hsp = Math.sqrt(
                0.299 * (r * r) +
                0.587 * (g * g) +
                0.114 * (b * b)
            );
            //light color
            if (hsp > 127.5 && land == false) {
                return r + "," + g + "," + b;
            }
            //dark color
            else if (hsp < 127.5 && land == true) {
                return r + "," + g + "," + b;
            }
        }
    }

    //these 2 functions interpolateColor(s) takes 2 colors and gives me 'steps' colors between
    function interpolateColors(color1, color2, steps) {
        var stepFactor = 1 / (steps - 1),
            interpolatedColorArray = [];
        color1 = color1.match(/\d+/g).map(Number);
        color2 = color2.match(/\d+/g).map(Number);

        for (var i = 0; i < steps; i++) {
            interpolatedColorArray.push(interpolateColor(color1, color2, stepFactor * i));
        }
        return interpolatedColorArray;
    }

    function interpolateColor(color1, color2, factor) {
        if (arguments.length < 3) {
            factor = 0.5;
        }
        var result = color1.slice();
        for (var i = 0; i < 3; i++) {
            result[i] = Math.round(result[i] + factor * (color2[i] - color1[i]));
        }
        return result;
    };

    //retrives a random color for land
    function rndLandColor() {
        return 'rgb(' + colorsLand[Math.floor(Math.random() * 5999) + 1] + ')';
    }
    //retrives a random color for water
    function rndWaterColor() {
        return 'rgb(' + colorsWater[Math.floor(Math.random() * 5999) + 1] + ')';
    }

    function drawPlanet() {
        var RAF;
        var i = 0, j = 0;
        function animate() {
            ctx.beginPath();

            //fill in holes in the land that is bigger then 1
            var score = 0;
            if (tiles[tileNum - (size + 1)] !== undefined && tiles[tileNum + (size + 1)] !== undefined) {
                if (tiles[tileNum].land == false) {
                    score++;
                }
                if (tiles[tileNum - 1].land == true) {
                    score++;
                }
                if (tiles[tileNum + 1].land == true) {
                    score++;
                }
                if (tiles[tileNum + (size + 1)].land == true) {
                    score++;
                }
                if (tiles[tileNum - (size + 1)].land == true) {
                    score++;
                }
            }

            if (score >= 3) {
                ctx.fillStyle = rndLandColor();
            }

            //cover single land tiles with water (if water tile is up,down,left and right of this tile)
            else if (
                tiles[tileNum - (size + 1)] !== undefined &&
                tiles[tileNum + (size + 1)] !== undefined &&
                tiles[tileNum - 1].land == false &&
                tiles[tileNum + 1].land == false &&
                tiles[tileNum - (size + 1)].land == false &&
                tiles[tileNum + (size + 1)].land == false) {
                ctx.fillStyle = rndWaterColor();
            }

            //cover single water tiles with land (if land tile is up,down,left and right of this tile)
            else if (
                tiles[tileNum - (size + 1)] !== undefined &&
                tiles[tileNum + (size + 1)] !== undefined &&
                tiles[tileNum - 1].land == true &&
                tiles[tileNum + 1].land == true &&
                tiles[tileNum - (size + 1)].land == true &&
                tiles[tileNum + (size + 1)].land == true) {
                ctx.fillStyle = rndLandColor();
            }
            //cover tile with land
            else if (tiles[tileNum] !== undefined && tiles[tileNum].land == true) {
                ctx.fillStyle = rndLandColor();
            }

            //cover tile with water
            else if (tiles[tileNum] !== undefined && tiles[tileNum].land == false) {
                ctx.fillStyle = rndWaterColor();
            }
            tileNum++;
            ctx.fill();
            ctx.closePath();
            ctx.fillRect(tileSize * j, tileSize * i, tileSize, tileSize);

            j++;
            if (j >= (size + 1)) {
                i += 1;
                j = 0;
                if (i >= (size + 1)) {
                    cancelAnimationFrame(RAF);
                }
            }
            RAF = requestAnimationFrame(function () {
                animate();
            });
        }
        animate();
}
#canvas {
        border: 10px solid #000000;
        border-radius: 50%;
        background-color: aquamarine;
    }

    .container {
        width: 720px;
        height: 720px;
        position: relative;
    }

    .gradient {
        position: absolute;
        height: 730px;
        width: 730px;
        top: 0;
        left: 0;
        border-radius: 50%;
        opacity: 0.8;
    }
<div class="container">
    <img class="gradient" src="https://www.mediafire.com/convkey/1f5a/cgu50lw1ehcp4fq6g.jpg" />
    <canvas id="canvas" width="710" height="710"></canvas>
</div>
0
pat 26 मई 2019, 16:59

1 उत्तर

सबसे बढ़िया उत्तर

पिक्सेल कला करने के लिए कैनवास आरेखण विधियों का उपयोग न करें।

पथ भरना एक अपेक्षाकृत धीमा ऑपरेशन है, fillRect() के माध्यम से पिक्सेल खींचना, लगभग कभी भी सही तरीका नहीं है।
इसके बजाय किसी को सीधे इमेजडाटा ऑब्जेक्ट में हेरफेर करना पसंद करना चाहिए, और इसे केवल एक बार कैनवास पर पेंट करना चाहिए।

यदि आपको एक स्केल सेट करने की आवश्यकता है, तो एक अनस्केल्ड इमेज बिटमैप का उपयोग करें, इसे अपने संदर्भ में रखें और फिर ड्रॉइमेज का उपयोग करके इसे अपस्केल करें।

यहां आपकी स्क्रिप्ट का एक अपडेट किया गया संस्करण है, जहां मैंने कुछ इतने मामूली नहीं सुधार लागू किए हैं, जैसे कि आउट-ऑफ़-स्क्रीन पिक्सेल के लिए रंग नहीं बनाना, साथ ही इस ImageData हेरफेर तकनीक के साथ।
यह अब इतनी तेजी से चलता है कि इसे सिंक्रोनाइज़ किया जा सके। लेकिन अगर आपको इसे और भी बेहतर करने की आवश्यकता है, तो ध्यान दें कि आपका getColor काफी अप्रभावी लगता है, लेकिन मैंने इसे छुआ नहीं।

var tileNum = 0;
var tiles;
var colorsLand;
var colorsWater;
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var tileSize = 2;
canvas.width = canvas.height = 710;
// 'size' should be your grid size, not the actual pixel size painted on screen
var size = Math.ceil(canvas.width / tileSize);


function generatePlanet() {
  tileNum = 0;
  tiles = [{
    x: 0,
    y: 0,
    land: false
  }];

  //Retrive colors        

  colorsLand = interpolateColors(getColor(true), getColor(true), 6000);
  colorsWater = interpolateColors(getColor(false), getColor(false), 6000);

  //Creates a array of my tiles and sets either water or land to them and calculates the % of being water/land
  for (var i = 0; i < (size * size); i++) {
    var currentTile = tiles[tiles.length - 1];
    if (currentTile.x <= (size - 1)) {
      var isLand = false;
      if (currentTile.land == true || tiles.length > size && tiles[tiles.length - size].land == true) {
        isLand = (Math.floor(Math.random() * 100) + 1) > 35;
      } else if (currentTile.land == true || tiles.length > size &&
        (tiles[tiles.length - 1].land == true ||
          tiles[tiles.length - size].land == true)) {
        isLand = (Math.floor(Math.random() * 100) + 1) > size;
      } else {
        isLand = (Math.floor(Math.random() * 100) + 1) > 99;
      }
      tiles.push({
        x: currentTile.x + 1,
        y: currentTile.y,
        land: isLand
      });
    } else {
      tiles.push({
        x: 0,
        y: currentTile.y + 1,
        land: isLand
      });
    }
  }
  drawPlanet()
}


//retrive a random color if it's a land tile i want it dark water i want light
function getColor(land) {
  while (true) {
    var r = Math.floor(Math.random() * 256) + 1
    var g = Math.floor(Math.random() * 256) + 1
    var b = Math.floor(Math.random() * 256) + 1
    var hsp = Math.sqrt(
      0.299 * (r * r) +
      0.587 * (g * g) +
      0.114 * (b * b)
    );
    //light color
    if (hsp > 127.5 && land == false) {
      return [r,g,b];
    }
    //dark color
    else if (hsp < 127.5 && land == true) {
      return [r,g,b];
    }
  }
}

//these 2 functions interpolateColor(s) takes 2 colors and gives me 'steps' colors between
function interpolateColors(color1, color2, steps) {
  var stepFactor = 1 / (steps - 1),
    interpolatedColorArray = [];

  for (var i = 0; i < steps; i++) {
    interpolatedColorArray.push(toUint32AARRGGBB(interpolateColor(color1, color2, stepFactor * i)));
  }
  return interpolatedColorArray;
}
function toUint32AARRGGBB(arr) {
  return Number('0xFF' + arr.map(toHexString2).join(''))
}
function toHexString2(val) {
  return val.toString(16)
    .padStart(2, '0'); // padStart may need a polyfill
}
function interpolateColor(color1, color2, factor) {
  if (arguments.length < 3) {
    factor = 0.5;
  }
  var result = color1.slice();
  for (var i = 0; i < 3; i++) {
    result[i] = Math.round(result[i] + factor * (color2[i] - color1[i]));
  }
  return result;
};

//retrives a random color for land
function rndLandColor() {
  return colorsLand[Math.floor(Math.random() * 5999) + 1];
}
//retrives a random color for water
function rndWaterColor() {
  return colorsWater[Math.floor(Math.random() * 5999) + 1];
}

// now drawing synchronously:
function drawPlanet() {

  var gridsize = size;
  var rad = gridsize / 2;
  
  // generate an ImageData, the size of our pixel grid
  var imgData = new ImageData(gridsize, gridsize);
  // work directly on Uint32 values (0xAARRGGBB on LittleEndian)
  var data = new Uint32Array(imgData.data.buffer);

  var score, y, x;
  for (y = 0; y < gridsize; y++) {
    for (x = 0; x < gridsize; x++) {
      score = 0;
      
      // if we are outside of the inner area
      if (Math.hypot(rad - x, rad - y) > rad + 2) {
        tileNum++;
        continue;
      }
      //fill in holes in the land that is bigger then 1
     if (tiles[tileNum - (gridsize + 1)] !== undefined && tiles[tileNum + (size + 1)] !== undefined) {
          if (tiles[tileNum].land == false) {
              score++;
          }
          if (tiles[tileNum - 1].land == true) {
              score++;
          }
          if (tiles[tileNum + 1].land == true) {
              score++;
          }
          if (tiles[tileNum + (gridsize + 1)].land == true) {
              score++;
          }
          if (tiles[tileNum - (gridsize + 1)].land == true) {
              score++;
          }
      }

      if (score >= 3) {
          color = rndLandColor();
      }

      //cover single land tiles with water (if water tile is up,down,left and right of this tile)
      else if (
          tiles[tileNum - (gridsize + 1)] !== undefined &&
          tiles[tileNum + (gridsize + 1)] !== undefined &&
          tiles[tileNum - 1].land == false &&
          tiles[tileNum + 1].land == false &&
          tiles[tileNum - (gridsize + 1)].land == false &&
          tiles[tileNum + (gridsize + 1)].land == false) {
          color = rndWaterColor();
      }

      //cover single water tiles with land (if land tile is up,down,left and right of this tile)
      else if (
          tiles[tileNum - (gridsize + 1)] !== undefined &&
          tiles[tileNum + (gridsize + 1)] !== undefined &&
          tiles[tileNum - 1].land == true &&
          tiles[tileNum + 1].land == true &&
          tiles[tileNum - (gridsize + 1)].land == true &&
          tiles[tileNum + (gridsize + 1)].land == true) {
          color = rndLandColor();
      }
      //cover tile with land
      else if (tiles[tileNum] !== undefined && tiles[tileNum].land == true) {
          color = rndLandColor();
      }

      //cover tile with water
      else if (tiles[tileNum] !== undefined && tiles[tileNum].land == false) {
          color = rndWaterColor();
      }
      tileNum++;
      data[(y * gridsize) + x] = color;
    }
  }
  // all done populating the ImageData
  // put it on the context at scale(1,1)
  ctx.putImageData(imgData, 0, 0);
  // remove antialiasing
  ctx.imageSmoothingEnabled = false;
  // up-scale
  ctx.scale(tileSize, tileSize);
  // draw the canvas over itself
  ctx.drawImage(ctx.canvas, 0, 0);
  ctx.setTransform(1, 0, 0, 1, 0, 0);
}

generatePlanet();
#canvas {
  border: 10px solid #000000;
  border-radius: 50%;
  background-color: aquamarine;
}

.container {
  width: 720px;
  height: 720px;
  position: relative;
}

.gradient {
  position: absolute;
  height: 730px;
  width: 730px;
  top: 0;
  left: 0;
  border-radius: 50%;
  opacity: 0.8;
}
<div class="container">
  <img class="gradient" src="https://www.mediafire.com/convkey/1f5a/cgu50lw1ehcp4fq6g.jpg" />
  <canvas id="canvas" width="710" height="710"></canvas>
</div>

अब, अगर मैं आपकी स्थिति में होता, तो मुझे लगता है कि मैं भी पूरी तरह से कहीं और देखना शुरू कर देता। आप जो करना चाहते हैं, उसके लिए ऐसा लगता है कि कुछ शोर जनरेटर अधिक यथार्थवादी आउटपुट के साथ अधिक कुशल हो सकते हैं।
ऐसा ही एक शोर जनरेटर SVG फ़िल्टर में उपलब्ध है , और इसलिए कैनवास2डी एपीआई के लिए सुलभ है, हालांकि मुझे यह स्वीकार करना होगा कि इसे नियंत्रित करना इतना आसान नहीं है।
लेकिन अगर आप इसे देखना चाहते हैं, तो यहां एक मोटा खेल का मैदान है:

const controls = new Set();

function randColor() {
  return '#' + (Math.floor((Math.random()*0xFFFFFF)))
    .toString(16)
    .padStart(6, 0);
}
function makeInput(type, options) {
  return Object.assign(document.createElement('input'), {type}, options);
}
class Control {
  constructor() {
    this.color = makeInput('color', {value: randColor()});
    this.freq = makeInput('range', {min: 0.0001, max:1, step: 0.0001, value: Math.random() / 20});
    this.numOctaves = makeInput('range', {min: 1, max:10, step: 1, value: 7});
    this.opacity = makeInput('range', {min:0.01, max:1, step: 0.001, value:1});
    this.seed = Math.random() * 1000;
    const remover = document.createElement('span');
    remover.textContent = 'x';
    remover.classList.add('remover');
    const container = document.createElement('div');
    container.classList.add('control');
    
    container.append(
      "color: ", this.color,
      "baseFrequency: ", this.freq,
      "numOctaves: ", this.numOctaves,
      "opacity", this.opacity,
      remover
    );
    
    document.querySelector('.controls').append(container);
    
    remover.onclick = e => {
      container.remove();
      controls.delete(this);
      draw();
    };
    this.color.oninput = this.freq.oninput =  this.numOctaves.oninput = this.opacity.oninput = draw;
  }
}
for(let i=0; i<3; i++) {
  controls.add(new Control());
}

const main = c.getContext('2d');
const ctx = c.cloneNode().getContext('2d');

main.arc(c.width/2, c.height/2, Math.min(c.width, c.height)/2,0,Math.PI*2);


draw();

add_control.onclick = e => {
  controls.add(new Control());
  draw();
}

function draw() {

  main.globalCompositeOperation = 'source-over';
  main.clearRect(0,0,c.width,c.height);

  controls.forEach(control => {
    ctx.globalCompositeOperation = 'source-over';
    ctx.filter = "none";
    ctx.clearRect(0,0,c.width,c.height);
    
    // update <filter>
    turb.setAttribute('seed', control.seed);
    turb.setAttribute('baseFrequency', control.freq.value);
    turb.setAttribute('numOctaves', control.numOctaves.value);
    // draw black and transp
    
    ctx.filter = "url(#myFilter)"
    ctx.fillRect(0,0,c.width, c.width);
    // do the composition with solid color
    ctx.filter = "none"
    ctx.fillStyle = control.color.value;
    ctx.globalCompositeOperation = 'source-in'
    ctx.fillRect(0,0,c.width, c.width);
    main.globalAlpha = control.opacity.value;
    // draw on visible context
    main.drawImage(ctx.canvas, 0,0)
    main.globalAlpha = 1;
  });
  // cut-out as a circle
  main.globalCompositeOperation = 'destination-in';
  main.fill()
}
.control {
  display: inline-block;
  border: 1px solid;
  padding: 6px;
  position: relative
}
.control input {
  display: block;
}
.control span {
  position: absolute;
  top: 6px;
  right: 6px;
  cursor: pointer;
}

#canvas {
  border: 10px solid #000000;
  border-radius: 50%;
  background-color: aquamarine;
}

.container {
  width: 360px;
  height: 360px;
  position: relative;
}

.gradient {
  position: absolute;
  height: 360px;
  width: 360px;
  top: 0;
  left: 0;
  border-radius: 50%;
  opacity: 0.8;
}
<div class="controls">
  <button id="add_control">add new layer</button><br>
</div>
<div class="container">
<canvas id="c" width="360" height="360"></canvas>
<svg>
  <filter id="myFilter">
    <feTurbulence type="fractalNoise" baseFrequency="0.045"
        id="turb" result="turb"/>
     <feComponentTransfer in="turb" result="contrast">
       <feFuncR type="linear" slope="1.6" intercept="-0.15"/>
       <feFuncG type="linear" slope="1.6" intercept="-0.15"/>
       <feFuncB type="linear" slope="1.6" intercept="-0.15"/>
     </feComponentTransfer>
    <feColorMatrix in="contrast"
      type="luminanceToAlpha" result="alpha"/>

  </filter>
</svg>
<img class="gradient" src="https://www.mediafire.com/convkey/1f5a/cgu50lw1ehcp4fq6g.jpg" />
</div>
3
Kaiido 27 मई 2019, 08:45