提高 P5js 中的半色调/粒子性能
Improving halftone / particles performance in P5js
我希望圆点能够顺畅地做出反应,所以我想知道是否有办法提高这段代码的性能。
我正在尝试创建一个等距的点网格,它既可以用作半色调效果(我已经达到),也可以用作对鼠标位置(重力/排斥)做出反应的粒子系统。
因为它应该表现得像半色调图像,所以点的密度应该保持相当高。
任何想法将不胜感激
let img;
let smallPoint, largePoint;
let res;
let manualBrightness = 6;
let lineLength = 1;
let row;
let gfg;
function preload() {
img = loadImage('https://i.imgur.com/Jvh1OQm.jpg');
}
function setup() {
createCanvas(400, 400);
smallPoint = 4;
largePoint = 40;
imageMode(CENTER);
noStroke();
background(0);
img.loadPixels();
res = 5;
row = 0;
gfg = new Array(floor((img.height)/res));
for (var i = 0; i < gfg.length; i++) {
gfg[i] = new Array(floor((img.height)/res));
}
var h = 0;
for (var i = 0; i < gfg.length; i++) {
row++;
let localI=i*res;
for (var j = 0; j < gfg[0].length; j++) {
let localJ = j*res*2*Math.sqrt(3);
// localJ=localJ+res*2*Math.sqrt(3);
gfg[i][j] = brightness(img.get(localJ, localI));
// console.log(gfg[i][j]);
}
}
}
function draw() {
background(0);
row = 0;
for (let i = 0; i<gfg.length; i++){
let localI = i*res;
row++;
for (let j = 0; j<gfg[i].length; j++){
let localJ = j*res*2*Math.sqrt(3);
if(row%2==0){
localJ=floor(localJ+res*Math.sqrt(3));
}
let pix = gfg[i][j];
// B = brightness(pix);
B=pix;
B=(1/300)*B*manualBrightness;
fill(255);
stroke(255);
strokeWeight(0);
ellipse(localJ, localI,2,2);
fill(255);
let ellipseSize =B*res*(mouseX/width);
// if(i%8==0 && (j+4)%8==0){
// ellipseSize = 4;
// }
ellipse(localJ, localI, ellipseSize,ellipseSize);
}
}
}
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.2.0/p5.js" integrity="sha512-cuCpFhuSthtmbmQ5JjvU7msRYynRI67jVHsQhTP8RY+H4BC9qa9kQJeHTomV9/QnOWJbDpLFKdbIHtqTomJJug==" crossorigin="anonymous"></script>
</head>
<body>
<main>
</main>
</body>
Tl;博士:web editor
在尝试提高代码效率时,最好的做法之一就是在循环期间尽可能少地执行代码。在 draw()
块中,您有一个嵌套的 for
循环。这实际上是一种双嵌套循环,因为 draw()
本身就是一个循环。这意味着在循环的最深层(当您遍历 j
时),您必须多次执行这些操作,并且每一帧。实际上可以将嵌套循环的最深部分减少到只有一个命令:在适当的位置和大小绘制圆。
我在这里提出的一些建议会大大降低您的代码的可读性。这些建议仅在您需要更快地做事时有用,为了保持可读性,我建议您发表评论。
您的代码中的一些示例:
- 对于每一个圆圈和每一帧,计算机将填充颜色设置两次(两次都设置为相同的颜色),将 strokeWeight 设置为 0,将描边颜色设置为 255。None of那是必要的。事实上,这些东西可以进入
setup()
块,因为它对每一帧都是相同的。
- 你在每个点和帧上画两个圆。如果第二个更大,则第一个是不可见的。您可能会对计算机在屏幕上绘制内容所花费的工作量感到惊讶。最好尽量减少:只画一个圆圈。您可以使用
max()
设置大小,也可以使用 circle()
代替 ellipse()
(我不知道使用 circle()
是否真的更快,但是我觉得它更好看):
circle(localJ, localI, max(2,ellipseSize));
- 这引出了我的下一点:声明变量需要工作。不要使用比你必须的更多的变量,并尝试准确地定义它们,当你使用它们时它们将如何。例如,在您的代码中,
ellipseSize
是您插入到 circle
函数中的变量,那么为什么不首先将其设为您想要的,而不定义 B 或 pix? B 和 pix 都没有用来做任何其他事情,所以我们可以这样做:
let ellipseSize = B*res*(mouseX/width);
-> remove line 61:
let ellipseSize = (1/300)*B*manualBrightness*res*(mouseX/width);
-> remove line 60:
let ellipseSize = (1/300)*pix*manualBrightness*res*(mouseX/width);
-> remove line 58:
let ellipseSize = (1/300)*gfg[i][j]*manualBrightness*res*(mouseX/width);
-> rearrange:
let ellipseSize = gfg[i][j]*((manualBrightness*res)/(width*300))*mouseX;
- 从这里开始,不需要每次都将
gfg[i][j]
乘以 (manualBrightness*res)/(width*300)
。这些价值观永远不会改变。我们在这里可以做的是将所有内容移至 gfg 的定义,第 37 行:
gfg[i][j] = brightness(img.get(localJ, localI));
->
gfg[i][j] = brightness(img.get(localJ, localI)) * (manualBrightness*res)/(width*300);
let ellipseSize = gfg[i][j]*mouseX;
- 现在,如果您查看使用 ellipseSize 的位置,您会发现它只在一个命令中使用了一次。这很难保证使用变量。我喜欢使用的一个规则是,如果你要使用它超过 3 次,你只创建一个变量。否则,它只会占用内存和时间。让我们看看是否有任何其他变量我们可以摆脱。
row
在此循环中做了什么?递增后,我们基本上只有 row = i+1
。此外,row
唯一用于检测奇偶校验,这很容易通过 i
:
完成
row%2==0
is the same as
i%2 == 1
所以没有必要让 row
出现在这个循环的任何地方;我们可以只使用 i
。
说到那个 if 语句,如果我们小心的话,我们实际上可以去掉它。
首先,我们可以摆脱那里的 floor 功能,这并没有什么帮助。
现在让我们想一想……当 i%2
为 1(而不是 0)时,我们要添加一些额外的东西。这和说
完全一样
localJ = localJ + (i%2)*res*Math.sqrt(3);
不需要 if 语句。但是如果我们去掉 if 语句,那么我们在一行中有两行我们正在为 localJ
赋值。我们可以压缩这两行:
let localJ = j*res*2*Math.sqrt(3);
if(i%2==1){
localJ = floor(localJ+res*Math.sqrt(3));
}
-> get rid of if statement
let localJ = j*res*2*Math.sqrt(3);
localJ = localJ+(i%2)*res*Math.sqrt(3);
-> combine these two lines
let localJ = j*res*2*Math.sqrt(3)+(i%2)*res*Math.sqrt(3);
-> factor out res*Math.sqrt(3)
let localJ = (2*j+(i%2))*res*Math.sqrt(3);
但是现在我们有两个变量在一个命令中只使用一次。这不保证使用变量:
let localJ = (2*j+(i%2))*res*Math.sqrt(3);
let ellipseSize = gfg[i][j]*mouseX;
circle(localJ, localI, max(2, ellipseSize));
-> just put the formulas into circle()
circle((2*j+(i%2))*res*Math.sqrt(3),
localI,
max(2, gfg[i][j]*mouseX)
);
现在我们已经做到了,嵌套循环的最深处只有一个命令。但我们可以做得更好!平方根函数是最难的基本算术函数之一,我们在这里一遍又一遍地做。所以让我们做一个变量!
let sqrtShortcut;
...
sqrtShortcut = res * Math.sqrt(3);
...
circle((2*j+(i%2))*sqrtShortcut,
localI,
max(2, gfg[i][j]*mouseX)
);
我对 localI
进行了相同的处理。我们还可以在这里做一件事,但不太明显。在JavaScript中,有一个数组方法叫做.map()
。它基本上将一个函数应用于数组的每个元素,并 returns 一个具有更改值的新数组。我不会详细介绍该应用程序,但它在下面的草图中。
至此,真的没什么可做的了,但是我摆脱了一些不用的变量和无用的命令。最后,它的运行速度比以前快了大约 5 倍。网络编辑器是 here.
我希望圆点能够顺畅地做出反应,所以我想知道是否有办法提高这段代码的性能。
我正在尝试创建一个等距的点网格,它既可以用作半色调效果(我已经达到),也可以用作对鼠标位置(重力/排斥)做出反应的粒子系统。
因为它应该表现得像半色调图像,所以点的密度应该保持相当高。 任何想法将不胜感激
let img;
let smallPoint, largePoint;
let res;
let manualBrightness = 6;
let lineLength = 1;
let row;
let gfg;
function preload() {
img = loadImage('https://i.imgur.com/Jvh1OQm.jpg');
}
function setup() {
createCanvas(400, 400);
smallPoint = 4;
largePoint = 40;
imageMode(CENTER);
noStroke();
background(0);
img.loadPixels();
res = 5;
row = 0;
gfg = new Array(floor((img.height)/res));
for (var i = 0; i < gfg.length; i++) {
gfg[i] = new Array(floor((img.height)/res));
}
var h = 0;
for (var i = 0; i < gfg.length; i++) {
row++;
let localI=i*res;
for (var j = 0; j < gfg[0].length; j++) {
let localJ = j*res*2*Math.sqrt(3);
// localJ=localJ+res*2*Math.sqrt(3);
gfg[i][j] = brightness(img.get(localJ, localI));
// console.log(gfg[i][j]);
}
}
}
function draw() {
background(0);
row = 0;
for (let i = 0; i<gfg.length; i++){
let localI = i*res;
row++;
for (let j = 0; j<gfg[i].length; j++){
let localJ = j*res*2*Math.sqrt(3);
if(row%2==0){
localJ=floor(localJ+res*Math.sqrt(3));
}
let pix = gfg[i][j];
// B = brightness(pix);
B=pix;
B=(1/300)*B*manualBrightness;
fill(255);
stroke(255);
strokeWeight(0);
ellipse(localJ, localI,2,2);
fill(255);
let ellipseSize =B*res*(mouseX/width);
// if(i%8==0 && (j+4)%8==0){
// ellipseSize = 4;
// }
ellipse(localJ, localI, ellipseSize,ellipseSize);
}
}
}
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.2.0/p5.js" integrity="sha512-cuCpFhuSthtmbmQ5JjvU7msRYynRI67jVHsQhTP8RY+H4BC9qa9kQJeHTomV9/QnOWJbDpLFKdbIHtqTomJJug==" crossorigin="anonymous"></script>
</head>
<body>
<main>
</main>
</body>
Tl;博士:web editor
在尝试提高代码效率时,最好的做法之一就是在循环期间尽可能少地执行代码。在 draw()
块中,您有一个嵌套的 for
循环。这实际上是一种双嵌套循环,因为 draw()
本身就是一个循环。这意味着在循环的最深层(当您遍历 j
时),您必须多次执行这些操作,并且每一帧。实际上可以将嵌套循环的最深部分减少到只有一个命令:在适当的位置和大小绘制圆。
我在这里提出的一些建议会大大降低您的代码的可读性。这些建议仅在您需要更快地做事时有用,为了保持可读性,我建议您发表评论。
您的代码中的一些示例:
- 对于每一个圆圈和每一帧,计算机将填充颜色设置两次(两次都设置为相同的颜色),将 strokeWeight 设置为 0,将描边颜色设置为 255。None of那是必要的。事实上,这些东西可以进入
setup()
块,因为它对每一帧都是相同的。 - 你在每个点和帧上画两个圆。如果第二个更大,则第一个是不可见的。您可能会对计算机在屏幕上绘制内容所花费的工作量感到惊讶。最好尽量减少:只画一个圆圈。您可以使用
max()
设置大小,也可以使用circle()
代替ellipse()
(我不知道使用circle()
是否真的更快,但是我觉得它更好看):
circle(localJ, localI, max(2,ellipseSize));
- 这引出了我的下一点:声明变量需要工作。不要使用比你必须的更多的变量,并尝试准确地定义它们,当你使用它们时它们将如何。例如,在您的代码中,
ellipseSize
是您插入到circle
函数中的变量,那么为什么不首先将其设为您想要的,而不定义 B 或 pix? B 和 pix 都没有用来做任何其他事情,所以我们可以这样做:
let ellipseSize = B*res*(mouseX/width);
-> remove line 61:
let ellipseSize = (1/300)*B*manualBrightness*res*(mouseX/width);
-> remove line 60:
let ellipseSize = (1/300)*pix*manualBrightness*res*(mouseX/width);
-> remove line 58:
let ellipseSize = (1/300)*gfg[i][j]*manualBrightness*res*(mouseX/width);
-> rearrange:
let ellipseSize = gfg[i][j]*((manualBrightness*res)/(width*300))*mouseX;
- 从这里开始,不需要每次都将
gfg[i][j]
乘以(manualBrightness*res)/(width*300)
。这些价值观永远不会改变。我们在这里可以做的是将所有内容移至 gfg 的定义,第 37 行:
gfg[i][j] = brightness(img.get(localJ, localI));
->
gfg[i][j] = brightness(img.get(localJ, localI)) * (manualBrightness*res)/(width*300);
let ellipseSize = gfg[i][j]*mouseX;
- 现在,如果您查看使用 ellipseSize 的位置,您会发现它只在一个命令中使用了一次。这很难保证使用变量。我喜欢使用的一个规则是,如果你要使用它超过 3 次,你只创建一个变量。否则,它只会占用内存和时间。让我们看看是否有任何其他变量我们可以摆脱。
row
在此循环中做了什么?递增后,我们基本上只有 row = i+1
。此外,row
唯一用于检测奇偶校验,这很容易通过 i
:
row%2==0
is the same as
i%2 == 1
所以没有必要让 row
出现在这个循环的任何地方;我们可以只使用 i
。
说到那个 if 语句,如果我们小心的话,我们实际上可以去掉它。
首先,我们可以摆脱那里的 floor 功能,这并没有什么帮助。
现在让我们想一想……当 i%2
为 1(而不是 0)时,我们要添加一些额外的东西。这和说
localJ = localJ + (i%2)*res*Math.sqrt(3);
不需要 if 语句。但是如果我们去掉 if 语句,那么我们在一行中有两行我们正在为 localJ
赋值。我们可以压缩这两行:
let localJ = j*res*2*Math.sqrt(3);
if(i%2==1){
localJ = floor(localJ+res*Math.sqrt(3));
}
-> get rid of if statement
let localJ = j*res*2*Math.sqrt(3);
localJ = localJ+(i%2)*res*Math.sqrt(3);
-> combine these two lines
let localJ = j*res*2*Math.sqrt(3)+(i%2)*res*Math.sqrt(3);
-> factor out res*Math.sqrt(3)
let localJ = (2*j+(i%2))*res*Math.sqrt(3);
但是现在我们有两个变量在一个命令中只使用一次。这不保证使用变量:
let localJ = (2*j+(i%2))*res*Math.sqrt(3);
let ellipseSize = gfg[i][j]*mouseX;
circle(localJ, localI, max(2, ellipseSize));
-> just put the formulas into circle()
circle((2*j+(i%2))*res*Math.sqrt(3),
localI,
max(2, gfg[i][j]*mouseX)
);
现在我们已经做到了,嵌套循环的最深处只有一个命令。但我们可以做得更好!平方根函数是最难的基本算术函数之一,我们在这里一遍又一遍地做。所以让我们做一个变量!
let sqrtShortcut;
...
sqrtShortcut = res * Math.sqrt(3);
...
circle((2*j+(i%2))*sqrtShortcut,
localI,
max(2, gfg[i][j]*mouseX)
);
我对 localI
进行了相同的处理。我们还可以在这里做一件事,但不太明显。在JavaScript中,有一个数组方法叫做.map()
。它基本上将一个函数应用于数组的每个元素,并 returns 一个具有更改值的新数组。我不会详细介绍该应用程序,但它在下面的草图中。
至此,真的没什么可做的了,但是我摆脱了一些不用的变量和无用的命令。最后,它的运行速度比以前快了大约 5 倍。网络编辑器是 here.