Canvas Javascript FloodFill algorithm left white pixels without color

☆樱花仙子☆ 提交于 2021-02-11 13:55:16

问题


I have been working on a drawing app like paint. I am in the part of the floodfill and I am using and algorithm that I found on internet, however, it left some white pixels near the line of the figure without color, detecting what happens, I found that those pixels are not white, but its a color very close. I tried putting the alpha value of those pixels on 1, in case that those were like a transparent color, but it doesn't works.

There is an example of the problem:

image of the problem

There is my code, it's a little messy, and have some functions that doesn't work, that I have used to solve the problem.

    var black = document.getElementById("black");
var blue = document.getElementById("blue");
var green = document.getElementById("green");
var red = document.getElementById("red");


var body = document.getElementById("body");
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle='#fff';
ctx.fillRect(0,0,canvas.width,canvas.height);
var rect = canvas.getBoundingClientRect();
var x = 0, y = 0, dib = false, bucket = false, beforecolor;
var colorpen="#000"
var range = 8;
var capline = 'round';
movs = new Array();

console.log(0xFF0000FF);


canvas.addEventListener('mousedown', function(e){

        x = Math.floor(e.pageX - rect.left);
        y = Math.floor(e.pageY - rect.top);
        

    if(bucket === true){

        floodFill(ctx, Math.floor(x), Math.floor(y), 0xFF008000);
    }
    else{
       movs.push("STOP");
        dib = true; 
    }
    
});

canvas.addEventListener('mousemove', function(e){

    detectcolorfill(e);

    if(dib === true){
             dibujar(x, y, Math.floor(e.pageX - rect.left), Math.floor(e.pageY - rect.top), colorpen, range);
             movs.push([x, y, Math.floor(e.pageX - rect.left), Math.floor(e.pageY - rect.top), colorpen, range]);
             x = Math.floor(e.pageX - rect.left);
             y = Math.floor(e.pageY - rect.top);

    }
});

canvas.addEventListener('mouseup', function(e){
    if(dib === true){
        dibujar(x, y, Math.floor(e.pageX - rect.left), Math.floor(e.pageY - rect.top), colorpen, range);
        
        async function mandar(){
            
           await movs.push([x, y, Math.floor(e.pageX - rect.left), Math.floor(e.pageY - rect.top), colorpen, range]);
           await movs.push("STOP");

        }

        mandar();

        
        x=0;
        y=0;
        dib = false;


    }
    

});



blue.addEventListener('click', detectcolor);
black.addEventListener('click', detectcolor);
green.addEventListener('click', detectcolor);
red.addEventListener('click', detectcolor);

    



function detectcolor(e){

    switch(e.target.dataset.color){
        case 'black': 
            colorpen = 'black'; 
            beforecolor = 'black'; 
            break;
        case 'blue': 
            colorpen = 'blue'; 
            beforecolor = 'blue'; 
            break;
        case 'green': 
            colorpen = 'green'; 
            beforecolor = 'green'; 
            break;
        case 'red': 
            colorpen = 'red'; 
            beforecolor = 'red'; 
            break;

    }

}

function othercolor(value){
    colorpen = value;
    beforecolor = value;
}

function linerange(value){
    range = value;
}

function keypress(value){
    switch(value.code){
        case 'NumpadAdd': range++; break;
        case 'NumpadSubtract': range > 1 ? range-- : 0; break;
    }
}

function borrador(){
    colorpen = 'white';
}

function pincel(){
    capline = 'round';
    colorpen = beforecolor;
    bucket = false;
}

function rgbToHex(r, g, b) {
    
     if (r > 255 || g > 255 || b > 255)
        throw "Invalid color component";
    return ((r << 16) | (g << 8) | b).toString(16);   

    
}

function cubeta(){
    
    bucket = true;
    
}

function getPixel(pixelData, x, y) {
    if (x < 0 || y < 0 || x >= pixelData.width || y >= pixelData.height) {
      return -1;  // impossible color
    } else {
      return pixelData.data[y * pixelData.width + x];
    }
  }
  
  function floodFill(ctx, x, y, fillColor) {
    // read the pixels in the canvas
    const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
    
    // make a Uint32Array view on the pixels so we can manipulate pixels
    // one 32bit value at a time instead of as 4 bytes per pixel
    const pixelData = {
      width: imageData.width,
      height: imageData.height,
      data: new Uint32Array(imageData.data.buffer),
    };
    
    // get the color we're filling
    const targetColor = getPixel(pixelData, x, y);
    console.log(targetColor + '-' + fillColor);
    
    // check we are actually filling a different color
    if (androidToRgba(targetColor) !== androidToRgba(fillColor)) {
    
      const pixelsToCheck = [x, y];
      while (pixelsToCheck.length > 0) {
        const y = pixelsToCheck.pop();
        const x = pixelsToCheck.pop();
        
        const currentColor = getPixel(pixelData, x, y);
        if (currentColor === targetColor) {
          pixelData.data[y * pixelData.width + x] = fillColor;
          pixelsToCheck.push(x + 1, y);
          pixelsToCheck.push(x - 1, y);
          pixelsToCheck.push(x, y + 1);
          pixelsToCheck.push(x, y - 1);
        }
      }
      
      // put the data back
      ctx.putImageData(imageData, 0, 0);
    }
  }

  function androidToRgba(color){
    const colorArray = []
    for(let i=0; i<4; i++){
      colorArray.push(color % 256)
      color>>>=8
    }
    colorArray.pop()
    const alpha = 1;
    return `rgba(${colorArray.reverse()},${alpha})`
  }



function detectcolorfill(e){
    var point = ctx.getImageData(e.pageX - rect.left, e.pageY - rect.top, 1, 1).data;
    var hex = "#" + ("000000" + rgbToHex(point[0], point[1], point[2])).slice(-6);
    color = hex;
    console.log(hex);
}


function dibujar(x1, y1, x2, y2, color, rango){

    ctx.beginPath();
    ctx.strokeStyle = color;
    ctx.lineWidth = rango;
    ctx.lineCap = capline;
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.stroke();
    ctx.closePath();

}

function borrarcanva(){

    ctx.fillStyle='#fff';
    ctx.fillRect(0,0,canvas.width,canvas.height);
    movs = [];

}

function deshacer(){
    if(movs.length === 1){
        movs = [];
    }

    for(j=0;j<2;j++){
        ctx.fillStyle='#fff';
        ctx.fillRect(0,0,canvas.width,canvas.height);

    if(movs.length !== 0){
        movs.pop();
        while(movs[movs.length-1] !== "STOP"){
            movs.pop();
        }
       
         for(i=0;i<movs.length;i++){
            if(movs[i] === "STOP"){
                i++;
            }
        
                dibujar(movs[i][0], movs[i][1], movs[i][2], movs[i][3], movs[i][4], movs[i][5]);
            
            

        }   
        
          
    }

    else{
        movs = [];
        return 0;
    }
    }



    

}

Thanks!


回答1:


The reason is that when you draw on a canvas, some anti-aliasing is applied, which involves colour-gradients.

Instead of comparing with ===, you could allow some tolerance. You could for instance use this formula:

const colorDiff = (a, b) => a === b ? 0 // quick exit
                          : ((a & 0xFF) - (b & 0xFF)) ** 2 
                          + (((a >>  8) & 0xFF) - ((b >>  8) & 0xFF)) ** 2 
                          + (((a >> 16) & 0xFF) - ((b >> 16) & 0xFF)) ** 2;

NB: this assumes that the alpha value is 255 (opaque).

Then in your code choose some value for a tolerance, between 0 and 100000, for example 1000, and replace:

if (currentColor === targetColor) {

with:

if (colorDiff(currentColor, targetColor) <= tolerance) {


来源:https://stackoverflow.com/questions/62825533/canvas-javascript-floodfill-algorithm-left-white-pixels-without-color

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!