我正在制作一个自动生成行星的脚本,请参见codepen for example。
但是我遇到的问题是我想使其像素化程度降低,如果我将图块的尺寸设置为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 :(得分:3)
请勿使用画布绘制方法执行像素画。
填充路径是一个相对较慢的操作,通过 fillRect()绘制像素几乎是不正确的方法。
相反,应该选择直接操作ImageData对象,并将其仅在画布上绘制一次。
如果需要设置比例,请使用未缩放的ImageBitmap,将其放在上下文中,然后使用drawImage对其进行放大。
这是脚本的更新版本,我在其中做了一些不太细的改进,例如不为屏幕外像素生成颜色,以及此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过滤器中有一个这样的noise generator,因此可以被Canvas2D API访问,但是我不得不承认,控制它并不是那么容易。
但是,如果您想看一看,这里是一个崎playground的操场:
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>