Алгоритм заливки заливочного инструмента в приложении электронной краски P5.js - PullRequest
0 голосов
/ 30 июня 2018

Я пытаюсь создать инструмент заливки для моего приложения для рисования, созданный с помощью Electron.js и P5.js для холста. каждый раз, когда пользователь нажимает на холст, а его переменная mode равна «fill», он вызывает функцию floodFill. я попытался реализовать алгоритм отсюда: https://en.wikipedia.org/wiki/Flood_fill#Pseudocode (рекурсивная реализация на основе стека (четырехсторонняя)), но у меня есть проблема - я достигаю максимального стека вызовов. То, как я отображаю все на экране, это то, что у меня есть массив с именем drawing, и есть функции конструктора для типов объектов, которые вы можете рисовать (Круг, Квадрат), поэтому я создал функцию конструктора Pixel, чтобы я мог отображать ее каждый раз, я не могу вызвать updatePixels(), потому что я рендерил все каждый кадр с помощью функции draw(). это моя функция заливки:

function floodFill(x, y, target_col, replace_col) {
   if (x >= 0 && y >= 0 && x <= width && y <= height) {
        let index = (x + y * width) * 4;
        if (target_col === replace_col) {
            return;
        } else if (
            pixels[index] !== target_col.levels[0] &&
            pixels[index + 1] !== target_col.levels[1] &&
            pixels[index + 2] !== target_col.levels[2] &&
            pixels[index + 3] !== target_col.levels[3]
        ) {
            return;
        } else {
            drawing.push(new Pixel(x, y, replace_col));
            floodFill(x, y - 1, target_col, replace_col);
            floodFill(x, y + 1, target_col, replace_col);
            floodFill(x - 1, y, target_col, replace_col);
            floodFill(x + 1, y, target_col, replace_col);
        }
    }
}

вот как я вызываю функцию:

if (mode === "fill") {
    loadPixels();
    let index = (mouseX + mouseY * width) * 4;

    // target_color = what the user wants to replace
    let target_color = color(
        pixels[index],
        pixels[index + 1],
        pixels[index + 2],
        pixels[index + 3]
    );
   // replacement_color = the color that will be instead of the target_color
   let replacement_color = color(current_color);
    floodFill(mouseX, mouseY, target_color, replacement_color);
}

Кстати, в функции floodFill в else if я использовал эту технику, потому что простое выполнение color(pixels[index], pixels[index + 1], pixels[index + 2], pixels[index + 3]) !== target_col не сработало

EDIT: Я понял, что у меня есть ошибка с функцией floodFill, мой else if почти никогда не верен, потому что он также пытается проверить, не равны ли альфы, и почти всегда они равны 255. поэтому я добавил к остальному, если это:

else if (
     (pixels[index] !== target_col.levels[0] &&
         pixels[index + 1] !== target_col.levels[1] &&
        pixels[index + 2] !== target_col.levels[2]) ||
    pixels[index + 3] !== target_col.levels[3]
 ) {

теперь, что происходит, если я пытаюсь заполнить что-то, оно рисует прямую линию, пока не достигнет другого цвета, но затем достигнет максимального размера стека, пример: paint возможно, это связано с тем, что в функции floodFill первый рекурсивный вызов, который я выполняю, - y - 1 (вверх). Я хотел бы услышать больше предложений

ДРУГОЕ РЕДАКТИРОВАНИЕ: я понял, что в floodFill в рекурсивном разделе я называю его y - 1, затем y + 1, что не имеет смысла, потому что оно идет вверх, а затем вниз означает, что оно остается в том же пикселе, поэтому я отредактировал его так, чтобы как это:

floodFill(x, y - 1, target_col, replace_col);
floodFill(x - 1, y, target_col, replace_col);
floodFill(x, y + 1, target_col, replace_col);
floodFill(x + 1, y, target_col, replace_col);

теперь это выглядит так: paint

1 Ответ

0 голосов
/ 18 июля 2018

Вот пример заполнения в p5.js.

нарисуйте замкнутую фигуру в полых и затем нажмите на нее, удерживая нажатой клавишу Shift.

var stack = [];
var oldColor;
var fillColor


function setup() {
  createCanvas(windowWidth, windowHeight);
  noSmooth();
  fillColor = color(0, 255, 0);
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}

function matches(c, x, y) {
  var i = 4 * (y * width + x);
  return (
    pixels[i + 0] === c[0] &&
    pixels[i + 1] === c[1] &&
    pixels[i + 2] === c[2] &&
    pixels[i + 3] === c[3]
  );
}

function draw() {
  if (!stack.length) return;

  var p = stack.pop();
  var x1 = p.x,
    y = p.y;
  while (x1 > 0 && matches(oldColor, x1 - 1, y)) x1--;

  var spanAbove = false,
    spanBelow = false;

  var x2 = x1 + 1;
  var ip = 4 * (y * width + x2);
  while (x2 < width && matches(oldColor, x2, y)) {
    for (var i = 0; i < 4; i++)
      pixels[ip++] = fillColor.levels[i];

    if (y > 0 && spanAbove !== matches(oldColor, x2, y - 1)) {
      if (!spanAbove) stack.push({
        x: x2,
        y: y - 1
      });
      spanAbove = !spanAbove;
    }
    if (y < height - 1 && spanBelow !== matches(oldColor, x2, y + 1)) {
      if (!spanBelow) stack.push({
        x: x2,
        y: y + 1
      });
      spanBelow = !spanBelow;
    }
    x2++;
  }

  updatePixels();
}

function mouseDragged() {
  if (keyIsDown(SHIFT)) return;
  stroke(255);
  strokeWeight(2);
  line(pmouseX, pmouseY, mouseX, mouseY);
}

function mouseClicked() {
  if (keyIsDown(SHIFT)) {
    oldColor = get(mouseX, mouseY);
    loadPixels();
    stack = [];
    stack.push({
      x: mouseX,
      y: mouseY
    });
  }
}
html,
body {
  margin: 0;
  padding: 0;
  border: none;
  background: black;
}

canvas {
  display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/p5.js"></script>
...