颜色填充工具 P5.js

Flood Fill tool P5.js

我又来了,因为我在我的绘图应用程序中尝试实现填充工具时仍然遇到问题。

我正在尝试使用 p5.js 制作一个相当简单的 2d 绘图应用程序,每个绘图工具都有自己的构造函数。我一直无法理解我做错了什么以及为什么它不起作用,这让我很沮丧。

我在这里阅读了几篇文章并遵循了 youtube 上的教程,但我仍然不太明白。我将包括我到目前为止所做的,以便您可以看到。具体来说,我不确定为 draw 函数写什么。我希望在按下鼠标时在 mouseX 和 mouseY 坐标上进行洪水填充。另外,我希望目标颜色是从单独的构造函数 ColourPalette() 中选择的颜色。

HTML:

        <!DOCTYPE html>
        <html>
          <head>
            <script src="lib/p5.min.js"></script>
            <script src="lib/p5.dom.js"></script>
        
            <script src="sketch.js"></script>
        
            <!-- add extra scripts below -->
...
            <script src="fillTool.js"></script>
        
          </body>
        </html>

草图文件:

function setup() {
    

    //create a canvas to fill the content div from index.html
    canvasContainer = select('#content');
    var c = createCanvas(canvasContainer.size().width, canvasContainer.size().height);
    c.parent("content");
    

    //create helper functions and the colour palette
    helpers = new HelperFunctions();
    colourP = new ColourPalette();

...
    toolbox.addTool(new FillTool());
    background(255);

}

function draw() {
    //call the draw function from the selected tool.
    //if there isn't a draw method the app will alert the user
    if (toolbox.selectedTool.hasOwnProperty("draw")) {
        toolbox.selectedTool.draw();
    } else {
        alert("it doesn't look like your tool has a draw method!");
    }
}

我需要帮助的洪水填充构造函数。我在控制台的第 112 行(在绘图函数内)收到“Uncaught ReferenceError: floodFill is not defined”,我对如何修复它有点困惑。:

function FillTool() {
    
//set an icon and a name for the object
this.icon = "assets/freehand.jpg";
this.name = "FillTool";
    
var colourNew = ColourPalette(colourP); //Placeholder - How do I do this?

    
function getPixelData(x,y){
  var colour = [];

  for (var i = 0; i < d; i++) {
    for (var j = 0; j < d; j++) {
      idx = 4 * ((y * d + j) * width * d + (x * d + i));
      colour[0] = pixels[idx];
      colour[1] = pixels[idx+1];
      colour[2] = pixels[idx+2];
      colour[3] = pixels[idx+3];
    }
  }

  return colour;
}

function setPixelData(x, y, colourNew) {
  for (var i = 0; i < d; i++) {
    for (var j = 0; j < d; j++) {
      idx = 4 * ((y * d + j) * width * d + (x * d + i));
      pixels[idx] = colourNew[0];
      pixels[idx+1] = colourNew[1];
      pixels[idx+2] = colourNew[2];
      pixels[idx+3] = colourNew[3];
    }
  }
}

function matchColour(xPos,yPos,oldColour){    
    var current = get(xPos,yPos);
    if(current[0] == oldColour[0] && current[1] == oldColour[1] && current[2] == oldColour[2] && current[3] == oldColour[3]){        
        return true;
    }    
}

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


function floodFill (xPos,yPos){
    loadPixels();
    colourOld = getPixelData(xPos, yPos);
    var stack = [];
    var pixelList = [];
    stack.push(xPos,yPos);
    pixelList.push(xPos,yPos);
    console.log(stack);

    while(stack.length > 0){
        var yPos1 = stack.pop();
        var xPos1 = stack.pop();
        setPixelData(xPos1,yPos1,colourNew);

        if(xPos1 + 1 <= width && xPos1 + 1 > 0 ){
            if(matchColour(xPos1+1,yPos1,colourOld) && checkPixel(xPos1+1,yPos1,pixelList)){
                stack.push(xPos1+1,yPos1);
                pixelList.push(xPos1+1,yPos1);
            }
        }

        if(xPos1+1 <= width && xPos1+1 > 0 ){
            if(matchColour(xPos1-1,yPos1,colourOld) && checkPixel(xPos1-1,yPos1,pixelList)){
                stack.push(xPos1-1,yPos1);
                pixelList.push(xPos1-1,yPos1);
            }
        }
        if(yPos1+1 <= height && yPos1+1 > 0 ){
            if(matchColour(xPos1,yPos1+1,colourOld) && checkPixel(xPos1,yPos1+1,pixelList)){
                stack.push(xPos1,yPos1+1);
                pixelList.push(xPos1,yPos1+1);
            }
        }

        if(yPos1-1 <= height && yPos1-1 > 0 ){
            if(matchColour(xPos1,yPos1-1,colourOld) && checkPixel(xPos1,yPos1-1,pixelList)){
                stack.push(xPos1,yPos1-1);
                pixelList.push(xPos1,yPos1-1);
            }
        }
    }

    updatePixels();
    console.log(pixelList);
}  
}

this.draw = function() {

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

对不起,如果它有点乱,它是我此刻大脑的准确表现。

函数 checkPixel 非常非常慢,因为 pixelArray 随着您绘制新像素而增长,因此每次验证新像素是否在堆栈中或是否已经绘制都需要更长的时间。

在 javascript 中可以使用对象 {} 来存储 key/value 对,例如:

var colours = { 'red':{'r':255,'g':0,'b':0,'a':255}, 'black':{'r':0,'g':0,'b':0,'a':255} };

并且调用方法 hasOwnPorperty 来验证密钥的存在非常快。

colours.hasOwnPorperty('red') // is true
colours.hasOwnPorperty('green') // is false

如果您在 colours 中有 1,000,000 种颜色,hasOwnPorpertycolours 中找到一种颜色不会比在 colours 中只有一种颜色花费更长的时间=]. (对于您的 checkPixel 版本,O(1) 与 O(n) 相反)

尝试点击圆圈内部...或外部

let toolbox, d;

function setup() {
  createCanvas(600, 400);
  d = pixelDensity();

  //create helper functions and the colour palette
  //...
  let colourRed = ColourPalette(255,0,0,255);
  //...

  toolbox = {'selectedTool': new FillTool() }; 

  toolbox.selectedTool.setColour(colourRed);

  background(255);

  push();
  strokeWeight(1);
  stroke(0);
  circle(75,75,100);
  noStroke();
  fill(0,255,0,255);            
  circle(125,75,100);
  pop();
}

function draw() {
  if (! toolbox.selectedTool.hasOwnProperty("draw")) {
    alert("it doesn't look like your tool has a draw method!");
    return;
  }

  toolbox.selectedTool.draw();
}

function FillTool() {
  let self = this;

  //set an icon and a name for the object
  self.icon = "assets/freehand.jpg";
  self.name = "FillTool";
  self.colour = ColourPalette(0,0,0,255);

  self.draw = function () {
    if (mouseIsPressed) {
      floodFill(mouseX, mouseY);
    }
  };

  self.setColour = function (col) {
    self.colour = col;
  };

  function matchColour (pos, oldColour) {
    var current = getPixelData(pos.x, pos.y);
    return (   current[0] === oldColour[0] && current[1] === oldColour[1] 
            && current[2] === oldColour[2] && current[3] === oldColour[3] );
  }

  function getKey (pos) {
    return ""+pos.x+"_"+pos.y;
  }

  function checkPixel(pos, positionSet) { 
    return ! positionSet.hasOwnProperty( getKey(pos) );
  }

  function floodFill (xPos, yPos) {

    var stack = [];
    var pixelList = {};

    var first = {'x':xPos,'y':yPos};
    stack.push( first );
    pixelList[ getKey(first) ] = 1;

    //console.log( JSON.stringify(stack) );

    loadPixels();
    var firstColour = getPixelData(xPos, yPos);

    while (stack.length > 0) {

      var pos1 = stack.pop();

      setPixelData(pos1.x, pos1.y, self.colour);

      var up = {'x':pos1.x,  'y':pos1.y-1};
      var dn = {'x':pos1.x,  'y':pos1.y+1};
      var le = {'x':pos1.x-1,'y':pos1.y};
      var ri = {'x':pos1.x+1,'y':pos1.y};

      if (0 <= up.y && up.y < height && matchColour(up, firstColour)) addPixelToDraw(up);
      if (0 <= dn.y && dn.y < height && matchColour(dn, firstColour)) addPixelToDraw(dn);
      if (0 <= le.x && le.x < width  && matchColour(le, firstColour)) addPixelToDraw(le);
      if (0 <= ri.x && ri.x < width  && matchColour(ri, firstColour)) addPixelToDraw(ri);
    }

    updatePixels();

    //console.log( JSON.stringify(pixelList) );

    function addPixelToDraw (pos) {

      if (checkPixel(pos, pixelList)  ) {
        stack.push( pos );
        pixelList[ getKey(pos) ] = 1;
      }
    }
  }  

}


function ColourPalette (r,g,b,a) { 
  var self = (this !== window ? this : {});
  if (arguments.length === 0) {
    self['0'] = 0; self['1'] = 0; self['2'] = 0; self['3'] = 0;
  } else if (arguments.length === 1) {
    self['0'] = r[0]; self['1'] = r[1]; self['2'] = r[2];  self['3'] = r[3]; 
  } else if (arguments.length === 4) {
    self['0'] = r; self['1'] = g; self['2'] = b; self['3'] = a;
  } else {
    return null;
  }
  return self;
}


function getPixelData (x, y) {
  var colour = [];
  for (var i = 0; i < d; ++i) {
    for (var j = 0; j < d; ++j) {
      let idx = 4 * ((y * d + j) * width * d + (x * d + i));
      colour[0] = pixels[idx];
      colour[1] = pixels[idx+1];
      colour[2] = pixels[idx+2];
      colour[3] = pixels[idx+3];
    }
  }
  return colour;
}

function setPixelData (x, y, colour) {
  for (var i = 0; i < d; ++i) {
    for (var j = 0; j < d; ++j) {
      let idx = 4 * ((y * d + j) * width * d + (x * d + i));
      pixels[idx]   = colour[0];
      pixels[idx+1] = colour[1];
      pixels[idx+2] = colour[2];
      pixels[idx+3] = colour[3];
    }
  }
}
body { background-color:#efefef; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.0.0/p5.min.js"></script>