【问题标题】:P5 Flood Fill AttemptP5 洪水填充尝试
【发布时间】:2018-08-19 23:34:29
【问题描述】:

我一直在尝试使用初始 mouseX 和 mouseY 值创建填充函数。在尝试使用 4 路递归方法并失败后,我发现一篇文章建议使用数组来存储 4 路值并使用 .push() 函数将它们排队。当数组有值时,该函数将使用 .pop() 函数为 x 和 y 设置新值,然后依次测试并更改为所需的颜色。

在尝试使用这种方法进行洪水填充后,我仍然在努力获得任何与速度有关的性能。我试图包含一个额外的检查历史记录函数来存储访问像素的历史记录,并防止任何重复的 x,y 坐标被推回队列中。但是(当使用控制台记录“找到匹配”时)该函数似乎没有找到任何重复。

我非常感谢有关如何改进我迄今为止编写的代码的任何指导。或者也许是对实施起来并不复杂的不同方法的建议。我很少使用 Stack Overflow,也希望获得有关如何格式化问题和代码以供将来参考的指导。感谢您的任何时间。

使用我包含的sketch.js,您可以看到填充功能在非常小的物体(第一个初始正方形)上是可以接受的,但随着尺寸的增加,性能会停止。

index.html

<!DOCTYPE html>
<html>
  <head>
    <script src="p5.min.js"></script>
    <script src="sketch.js"></script>
    <style> body {padding: 0; margin: 0;} </style>
  </head>
  <body>
  </body>
</html>

sketch.js

var colorNew = [0,255,0,255];
function setup()
{
    createCanvas(500,500);
    squares();
}

function squares(){

    background(255); 
    noFill();
    stroke(0);
    rect(125,125,10,10);
    rect(125,125,20,20);
    rect(125,125,30,30);
    rect(125,125,40,40);
    rect(125,125,50,50);
    updatePixels();
}

function getPixData(xPos,yPos){    
    colorOld = get(xPos,yPos);
};

function checkValue(xPos,yPos,oldColor){    
    var currentPix
    currentPix = get(xPos,yPos);
    if(currentPix[0] == oldColor[0] && currentPix[1] == oldColor[1] && currentPix[2] == oldColor[2] && currentPix[3] == oldColor[3]){        
        return true;
    }    
};

function checkHistory(x1,y1,historyArray){    
    for (var i = 0 ; i < historyArray.length; i+=2){        
        if(x1 == historyArray[i] && y1 == historyArray[i+1]){            
            console.log("match found");           
            return false;                    
           }else {               
           console.log(historyArray.length)
           return true;             
            }
    } 
};


function floodFill (xPos,yPos){ 
    getPixData(mouseX,mouseY);    
    var stack = [];
    var historyList = [];
    stack.push(xPos,yPos);
    historyList.push(xPos,yPos);    
    console.log(stack);

    while(stack.length> 0){
        var yPos1 = stack.pop();
        var xPos1 = stack.pop();   
        set(xPos1,yPos1,colorNew); 
        updatePixels();

        if(xPos1+1 <= width && xPos1+1 > 0 ){
            if(checkValue(xPos1+1,yPos1,colorOld) && checkHistory(xPos1+1,yPos1,historyList)){
                stack.push(xPos1+1,yPos1);
                historyList.push(xPos1+1,yPos1);
                console.log("right checked")            
            }
        }

        if(xPos1+1 <= width && xPos1+1 > 0 ){
            if(checkValue(xPos1-1,yPos1,colorOld) && checkHistory(xPos1-1,yPos1,historyList)){
                stack.push(xPos1-1,yPos1);
                historyList.push(xPos1-1,yPos1);
                console.log("left checked");            
            }
        }
        if(yPos1+1 <= height && yPos1+1 > 0 ){
            if(checkValue(xPos1,yPos1+1,colorOld) && checkHistory(xPos1,yPos1+1,historyList)){
                stack.push(xPos1,yPos1+1);
                historyList.push(xPos1,yPos1+1);
                console.log("above checked");         
            }
        }

        if(yPos1-1 <= height && yPos1-1 > 0 ){
            if(checkValue(xPos1,yPos1-1,colorOld) && checkHistory(xPos1,yPos1-1,historyList)){
                stack.push(xPos1,yPos1-1);
                historyList.push(xPos1,yPos1-1);
                console.log("below checked");
            }
        }
    }      
    console.log(historyList);     
}

function draw()
{

    if(mouseIsPressed){
        floodFill(mouseX,mouseY)
    }

} 

【问题讨论】:

    标签: javascript stack p5.js flood-fill


    【解决方案1】:

    您的代码行为方式肯定有问题。几个观察:

    history 数组比它应该的大得多。从理论上讲,您只添加每个像素一次,对吗?但是检查history 数组的大小并注意它比像素数大得多。由于您将 xy 值都添加到数组中,因此对于 10x10 矩形,数组中应该有 200 个元素。但你最终会得到更多。

    我也会避免像这样将 x 和 y 分量添加到数组中。相反,让每个索引都包含一个点对象。像这样的:

    stack.push({x:xPos,y:yPos});
    

    这实际上并不能解决您的问题,但它会使调试更容易。您的代码肯定包含错误。稍后我会继续寻找更多内容,但希望这能让您走上正轨。

    【讨论】:

      【解决方案2】:

      主要的性能瓶颈是get(x,y) is slow。每次查看画布的像素数据都要调用getImageData,耗时约4ms。

      洪水填充算法必须检查每个像素一次,因此在 20x50 的正方形中,您必须检查 1000 个像素。如果每个像素检查需要 4 毫秒来获取颜色数据,那就是 4 秒!

      p5 建议几个performance optimization techniques 处理数据,其中之一是不要经常检查图像的像素。它建议提前缓存像素。幸运的是,p5 已经为您完成了这项工作。

      p5 在pixels 数组中创建一个包含图像所有颜色的数组。您可以使用pixel 数组,而不是使用get 查看像素数据。文档提供了如何从点 x,y 设置/获取颜色数据的代码:

      function getPixData(x,y){
        var color = [];
      
        for (var i = 0; i < d; i++) {
          for (var j = 0; j < d; j++) {
            // loop over
            idx = 4 * ((y * d + j) * width * d + (x * d + i));
            color[0] = pixels[idx];
            color[1] = pixels[idx+1];
            color[2] = pixels[idx+2];
            color[3] = pixels[idx+3];
          }
        }
      
        return color;
      };
      
      function setPixData(x, y, colorNew) {
        for (var i = 0; i < d; i++) {
          for (var j = 0; j < d; j++) {
            // loop over
            idx = 4 * ((y * d + j) * width * d + (x * d + i));
            pixels[idx] = colorNew[0];
            pixels[idx+1] = colorNew[1];
            pixels[idx+2] = colorNew[2];
            pixels[idx+3] = colorNew[3];
          }
        }
      }
      

      如果您将get(x,y)set(x,y) 的引用替换为这两个方法,并在setup 方法中调用loadPixels,您将看到速度的巨大提升。并且不要忘记在方法结束时调用updatePixels

      function floodFill (xPos,yPos){
          colorOld = getPixData(xPos, yPos);
          var stack = [];
          var historyList = [];
          stack.push(xPos,yPos);
          historyList.push(xPos,yPos);
          console.log(stack);
      
          while(stack.length> 0){
              var yPos1 = stack.pop();
              var xPos1 = stack.pop();
              setPixData(xPos1,yPos1,colorNew);
      
              if(xPos1+1 <= width && xPos1+1 > 0 ){
                  if(checkValue(xPos1+1,yPos1,colorOld) && checkHistory(xPos1+1,yPos1,historyList)){
                      stack.push(xPos1+1,yPos1);
                      historyList.push(xPos1+1,yPos1);
                      console.log("right checked")
                  }
              }
      
              if(xPos1+1 <= width && xPos1+1 > 0 ){
                  if(checkValue(xPos1-1,yPos1,colorOld) && checkHistory(xPos1-1,yPos1,historyList)){
                      stack.push(xPos1-1,yPos1);
                      historyList.push(xPos1-1,yPos1);
                      console.log("left checked");
                  }
              }
              if(yPos1+1 <= height && yPos1+1 > 0 ){
                  if(checkValue(xPos1,yPos1+1,colorOld) && checkHistory(xPos1,yPos1+1,historyList)){
                      stack.push(xPos1,yPos1+1);
                      historyList.push(xPos1,yPos1+1);
                      console.log("above checked");
                  }
              }
      
              if(yPos1-1 <= height && yPos1-1 > 0 ){
                  if(checkValue(xPos1,yPos1-1,colorOld) && checkHistory(xPos1,yPos1-1,historyList)){
                      stack.push(xPos1,yPos1-1);
                      historyList.push(xPos1,yPos1-1);
                      console.log("below checked");
                  }
              }
          }
      
          updatePixels();
          console.log(historyList);
      }
      

      另外,我建议将draw 函数更改为使用mouseClicked 而不是mousePressed,因为mousePressed 将在按住鼠标时持续运行,而mouseClicked 将在用户使用后仅运行一次放开鼠标。

      【讨论】:

      • 感谢您花时间查看我的代码,对于之前的错误回复深表歉意,因为该评论是为了另一个回复。在查看代码并插入 2 个使用上述公式的新函数 getPix() 和 setPix() 之后。 setPix() 函数本身做了微小的改进。当使用新的 getPix();我现在在控制台中收到“在潜在的内存不足崩溃之前暂停”错误。 (在不更改原始问题的情况下显示已编辑代码的最佳方法是什么?)
      • @David 我认为您的问题是这个答案和我的答案所谈论的内容的结合。我的回答提到您正在向历史记录添加额外的像素。我猜这是你的记忆问题的原因。
      猜你喜欢
      • 2020-12-27
      • 1970-01-01
      • 2023-03-18
      • 1970-01-01
      • 1970-01-01
      • 2019-08-22
      • 2013-01-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多