改进 P5js 中的半色调/粒子性能

如何解决改进 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,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。这些都不是必需的。事实上,这些东西可以进入 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)。这些价值观永远不会改变。我们在这里可以做的是将所有内容移至第 37 行的 gfg 定义:
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 语句,那么我们将连续两行给 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,ellipseSize));
-> just put the formulas into circle()
circle((2*j+(i%2))*res*Math.sqrt(3),gfg[i][j]*mouseX)
      );

现在我们已经做到了,嵌套循环的最深部分只有一个命令。但我们可以做得更好!平方根函数是最难的基本算术函数之一,我们在这里一遍又一遍地做。所以让我们创建一个变量!

let sqrtShortcut;
...
sqrtShortcut = res * Math.sqrt(3);
...
circle((2*j+(i%2))*sqrtShortcut,gfg[i][j]*mouseX)
      );

我对 localI 执行了相同的过程。我们还可以在这里做一件事,但不太明显。在 JavaScript 中,有一个名为 .map() 的数组方法。它基本上将一个函数应用于数组的每个元素,并返回一个具有更改值的新数组。我不会详细介绍应用程序,但它在下面的草图中。

此时,真的没有什么可做的了,但我摆脱了一些未使用的变量和无用的命令。最后,它的运行速度比以前快了大约 5 倍。网络编辑器是 here

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?