画布 - floodfill在边缘留下白色像素

时间:2016-06-07 12:12:20

标签: javascript canvas colors flood-fill

我正在创建一个绘图应用。我已经成功地做了一切。当我用深色绘制图像时,边缘会出现一些白色像素。我试图改变r + g + b的值以及alpha但没有用。谁能帮我吗?您可以从here查看实际网站。任何会帮助我的人,我都会给予他/她50美元的赏金。谢谢。这是我的代码。

<script type="text/javascript">
    var initial = screen.width - 50;

    if (initial < 1000) {
        initial = 1000;
    }

    var firsttime = null;

    var colorYellow = {
        r: 255,
        g: 207,
        b: 51
    };

    var context;
    var canvasWidth = initial;
    var canvasHeight = initial;
    var myColor = colorYellow;
    var curColor = myColor;
    var outlineImage = new Image();
    var backgroundImage = new Image();
    var drawingAreaX = 0;
    var drawingAreaY = 0;
    var drawingAreaWidth = initial;
    var drawingAreaHeight = initial;
    var colorLayerData;
    var outlineLayerData;
    var totalLoadResources = 2;
    var curLoadResNum = 0;
    var undoarr = new Array();
    var redoarr = new Array();

    function history(command) { // handles undo/redo button events.
        var data;
        if (command === "redo") {
            data = historyManager.redo(); // get data for redo
        } else
        if (command === "undo") {
            data = historyManager.undo(); // get data for undo
        }
        if (data !== undefined) { // if data has been found
            setColorLayer(data); // set the data
        }
    }

    // sets colour layer and creates copy into colorLayerData
    function setColorLayer(data) {
        context.putImageData(data, 0, 0);
        colorLayerData = context.getImageData(0, 0, canvasWidth, canvasHeight);
        context.drawImage(backgroundImage, 0, 0, canvasWidth, canvasHeight);
        context.drawImage(outlineImage, 0, 0, drawingAreaWidth, drawingAreaHeight);
    }

    // Clears the canvas.
    function clearCanvas() {
        context.clearRect(0, 0, context.canvas.width, context.canvas.height);
    }



    // Draw the elements on the canvas
    function redraw() {
        uc = 0;
        rc = 0;
        var locX,
            locY;

        // Make sure required resources are loaded before redrawing
        if (curLoadResNum < totalLoadResources) {
            return; // To check if images are loaded successfully or not.
        }

        clearCanvas();
        // Draw the current state of the color layer to the canvas
        context.putImageData(colorLayerData, 0, 0);

        historyManager.push(context.getImageData(0, 0, canvasWidth, canvasHeight));
        redoarr = new Array();
        // Draw the background
        context.drawImage(backgroundImage, 0, 0, canvasWidth, canvasHeight);

        // Draw the outline image on top of everything. We could move this to a separate 
        //   canvas so we did not have to redraw this everyime.
        context.drawImage(outlineImage, 0, 0, drawingAreaWidth, drawingAreaHeight);
    };

    function matchOutlineColor(r, g, b, a) {

        return (r + g + b < 50 && a >= 50);
    };

    function matchStartColor(pixelPos, startR, startG, startB) {

        var r = outlineLayerData.data[pixelPos],
            g = outlineLayerData.data[pixelPos + 1],
            b = outlineLayerData.data[pixelPos + 2],
            a = outlineLayerData.data[pixelPos + 3];

        // If current pixel of the outline image is black
        if (matchOutlineColor(r, g, b, a)) {
            return false;
        }

        r = colorLayerData.data[pixelPos];
        g = colorLayerData.data[pixelPos + 1];
        b = colorLayerData.data[pixelPos + 2];

        // If the current pixel matches the clicked color
        if (r === startR && g === startG && b === startB) {
            return true;
        }

        // If current pixel matches the new color
        if (r === curColor.r && g === curColor.g && b === curColor.b) {
            return false;
        }

        return true;
    };

    function colorPixel(pixelPos, r, g, b, a) {
        colorLayerData.data[pixelPos] = r;
        colorLayerData.data[pixelPos + 1] = g;
        colorLayerData.data[pixelPos + 2] = b;
        colorLayerData.data[pixelPos + 3] = a !== undefined ? a : 255;
    };

    function floodFill(startX, startY, startR, startG, startB) {

        document.getElementById('color-lib-1').style.display = "none";
        document.getElementById('color-lib-2').style.display = "none";
        document.getElementById('color-lib-3').style.display = "none";
        document.getElementById('color-lib-4').style.display = "none";
        document.getElementById('color-lib-5').style.display = "none";

        change = false;

        var newPos,
            x,
            y,
            pixelPos,
            reachLeft,
            reachRight,
            drawingBoundLeft = drawingAreaX,
            drawingBoundTop = drawingAreaY,
            drawingBoundRight = drawingAreaX + drawingAreaWidth - 1,
            drawingBoundBottom = drawingAreaY + drawingAreaHeight - 1,
            pixelStack = [
                [startX, startY]
            ];

        while (pixelStack.length) {

            newPos = pixelStack.pop();
            x = newPos[0];
            y = newPos[1];

            // Get current pixel position
            pixelPos = (y * canvasWidth + x) * 4;

            // Go up as long as the color matches and are inside the canvas
            while (y >= drawingBoundTop && matchStartColor(pixelPos, startR, startG, startB)) {
                y -= 1;
                pixelPos -= canvasWidth * 4;
            }

            pixelPos += canvasWidth * 4;
            y += 1;
            reachLeft = false;
            reachRight = false;

            // Go down as long as the color matches and in inside the canvas
            while (y <= drawingBoundBottom && matchStartColor(pixelPos, startR, startG, startB)) {
                y += 1;

                colorPixel(pixelPos, curColor.r, curColor.g, curColor.b);

                if (x > drawingBoundLeft) {
                    if (matchStartColor(pixelPos - 4, startR, startG, startB)) {
                        if (!reachLeft) {
                            // Add pixel to stack
                            pixelStack.push([x - 1, y]);
                            reachLeft = true;
                        }

                    } else if (reachLeft) {
                        reachLeft = false;
                    }
                }

                if (x < drawingBoundRight) {
                    if (matchStartColor(pixelPos + 4, startR, startG, startB)) {
                        if (!reachRight) {
                            // Add pixel to stack
                            pixelStack.push([x + 1, y]);
                            reachRight = true;
                        }
                    } else if (reachRight) {
                        reachRight = false;
                    }
                }

                pixelPos += canvasWidth * 4;
            }
        }
    };

    // Start painting with paint bucket tool starting from pixel specified by startX and startY
    function paintAt(startX, startY) {

        var pixelPos = (startY * canvasWidth + startX) * 4,
            r = colorLayerData.data[pixelPos],
            g = colorLayerData.data[pixelPos + 1],
            b = colorLayerData.data[pixelPos + 2],
            a = colorLayerData.data[pixelPos + 3];

        if (r === curColor.r && g === curColor.g && b === curColor.b) {
            // Return because trying to fill with the same color
            return;
        }

        if (matchOutlineColor(r, g, b, a)) {
            // Return because clicked outline
            return;
        }

        floodFill(startX, startY, r, g, b);

        redraw();
    };

    // Add mouse event listeners to the canvas
    function createMouseEvents() {

        $('#canvas').mousedown(function(e) {

            // Mouse down location
            var mouseX = e.pageX - this.offsetLeft,
                mouseY = e.pageY - this.offsetTop;

            // assuming that the mouseX and mouseY are the mouse coords.
            if (this.style.width) { // make sure there is a width in the style 
                // (assumes if width is there then height will be too
                var w = Number(this.style.width.replace("px", "")); // warning this will not work if size is not in pixels
                var h = Number(this.style.height.replace("px", "")); // convert the height to a number
                var pixelW = this.width; // get  the canvas resolution
                var pixelH = this.height;
                mouseX = Math.floor((mouseX / w) * pixelW); // convert the mouse coords to pixel coords
                mouseY = Math.floor((mouseY / h) * pixelH);
            }

            if ((mouseY > drawingAreaY && mouseY < drawingAreaY + drawingAreaHeight) && (mouseX <= drawingAreaX + drawingAreaWidth)) {
                paintAt(mouseX, mouseY);
            }
        });
    };

    resourceLoaded = function() {

        curLoadResNum += 1;
        //if (curLoadResNum === totalLoadResources) {
        createMouseEvents();
        redraw();
        //}
    };

    var historyManager = (function() { // Anon for private (closure) scope
        var uBuffer = []; // this is undo buff
        var rBuffer = []; // this is redo buff
        var currentState = undefined; // this holds the current history state
        var undoElement = undefined;
        var redoElement = undefined;
        var manager = {
            UI: { // UI interface just for disable and enabling redo undo buttons
                assignUndoButton: function(element) {
                    undoElement = element;
                    this.update();
                },
                assignRedoButton: function(element) {
                    redoElement = element;
                    this.update();
                },
                update: function() {
                    if (redoElement !== undefined) {
                        redoElement.disabled = (rBuffer.length === 0);
                    }
                    if (undoElement !== undefined) {
                        undoElement.disabled = (uBuffer.length === 0);
                    }
                }
            },
            reset: function() {
                uBuffer.length = 0;
                rBuffer.length = 0;
                currentState = undefined;
                this.UI.update();
            },
            push: function(data) {
                if (currentState !== undefined) {
                    var same = true
                    for (i = 0; i < data.data.length; i++) {
                        if (data.data[i] !== currentState.data[i]) {
                            same = false;
                            break;
                        }
                    }
                    if (same) {
                        return;
                    }
                }
                if (currentState !== undefined) {
                    uBuffer.push(currentState);
                }
                currentState = data;
                rBuffer.length = 0;
                this.UI.update();
            },
            undo: function() {
                if (uBuffer.length > 0) {
                    if (currentState !== undefined) {
                        rBuffer.push(currentState);
                    }
                    currentState = uBuffer.pop();
                }
                this.UI.update();
                return currentState; // return data or unfefined
            },
            redo: function() {
                if (rBuffer.length > 0) {
                    if (currentState !== undefined) {
                        uBuffer.push(currentState);
                    }
                    currentState = rBuffer.pop();
                }
                this.UI.update();
                return currentState;
            },
        }
        return manager;
    })();

    function start() {

        var canvas = document.createElement('canvas');
        canvas.setAttribute('width', canvasWidth);
        canvas.setAttribute('height', canvasHeight);
        canvas.setAttribute('id', 'canvas');
        document.getElementById('canvasDiv').appendChild(canvas);

        if (typeof G_vmlCanvasManager !== "undefined") {
            canvas = G_vmlCanvasManager.initElement(canvas);
        }
        context = canvas.getContext("2d");
        backgroundImage.onload = resourceLoaded();
        backgroundImage.src = "images/t.png";

        outlineImage.onload = function() {
            context.drawImage(outlineImage, drawingAreaX, drawingAreaY, drawingAreaWidth, drawingAreaHeight);

            try {
                outlineLayerData = context.getImageData(0, 0, canvasWidth, canvasHeight);
            } catch (ex) {
                window.alert("Application cannot be run locally. Please run on a server.");
                return;
            }
            clearCanvas();
            colorLayerData = context.getImageData(0, 0, canvasWidth, canvasHeight);
            resourceLoaded();
        };
        outlineImage.src = "images/products/<?php echo $product['product_image']; ?>";
    };

    if (historyManager !== undefined) {
        // only for visual feedback and not required for the history manager to function.
        historyManager.UI.assignUndoButton(document.querySelector("#undo-button"));
        historyManager.UI.assignRedoButton(document.querySelector("#redo-button"));
    }

    getColor = function() {

    };
</script>

4 个答案:

答案 0 :(得分:2)

是的,&#34;白色&#34;遗漏的斑点实际上并不是白色,因为白色和黑色之间会出现一个微小的渐变。试着在这些方面给它一些余地:

 if ((r <= curColor.r + 10 && r >= curColor.r - 10)  && (r >= curColor.g - 10 && r <= curColor.g + 10) && (b >= curColor.b - 10 && b <= curColor.b + 10)) {
        return false;
    }

您可以修改10因子,直到它看起来不错。只需调整它直到它没问题。 (可能是糟糕的代码,我刚刚醒来,但你应该得到图片:D)

您还可以在单​​独的缓冲区中预处理图像并减少颜色数量。这样就可以更容易地填充渐变的开头,从而减少或消除您所描述的不良影响。

答案 1 :(得分:2)

老实说,绘图程序的错误并不是因为绘制图像的错误。 “白色”像素实际上是浅灰色,是用于在图像中绘制线条的画笔工具的副作用。有两种方法:

  1. 从图像中删除所有淡灰色像素并使其变白。使用颜色选择工具和铅笔工具将解决这个问题。唯一的副作用是某些点的线条可能看起来有点生涩。

  2. 在涉及到什么颜色的时候给予一些宽容。所以,不要只是替换直白色,而是替换浅灰色。任何颜色#CCC(或rgb(204,204,204))都应该着色。

  3. 选项二的代码如下:

    if(r >= 204 && g >= 204 && b >= 204 && r === g && g === b){
        return true;
    }
    

    这只是检查像素是否为浅灰度颜色,如果是,则返回true。使用它代替轮廓颜色检查功能。

答案 2 :(得分:2)

我建议进行两项修改:

  1. Blend pixel&amp;填充颜​​色而不是硬覆盖
  2. 根据强度梯度变化而不是简单阈值来限制填充区域
  3. 在水平和垂直方向上填充,直到强度梯度的符号从+到 - 或 - 翻转到+,让我们填充整个区域,包括黑色边框的“一半”。通过检查梯度,我们确保不要超越强度最小值,从而避免填充邻近区域。

    看看以下演示:

    // Get pixel intensity:
    function getIntensity(data, i) {
      return data[i] + data[i + 1] + data[i + 2];
    }
    
    // Set pixel color:
    function setColor(data, i, r, g, b) {
      data[i] &= r;
      data[i + 1] &= g;
      data[i + 2] &= b;
    }
    
    // Fill a horizontal line:
    function fill(x, y, data, width, r, g, b) {
      var i_start = y * (width << 2);
      var i = i_start + (x << 2);
      var i_end = i_start + (width << 2);
      var i_intensity = getIntensity(data, i);
    
      // Horizontal line to the right:
      var prev_gradient = 0;
      var prev_intensity = i_intensity;
      for (var j = i; j < i_end; j += 4) {
        var intensity = getIntensity(data, j);
        gradient = intensity - prev_intensity;
        prev_intensity = intensity;
        if ((prev_gradient > 0 && gradient < 0) || (prev_gradient < 0 && gradient > 0)) break;
        if (gradient != 0) prev_gradient = gradient;
    
        setColor(data, j, 255, 0, 0);
      }
    
      // Horizontal line to the left:
      prev_gradient = 0;
      prev_intensity = i_intensity;
      for (var j = i - 4; j > i_start; j -= 4) {
        var intensity = getIntensity(data, j);
        gradient = intensity - prev_intensity;
        prev_intensity = intensity;
        if ((prev_gradient > 0 && gradient < 0) || (prev_gradient < 0 && gradient > 0)) break;
        if (gradient != 0) prev_gradient = gradient;
    
        setColor(data, j, 255, 0, 0);
      }
    }
    
    // Demo canvas:
    var canvas = document.getElementById('canvas');
    var context = canvas.getContext('2d');
    
    // Fill horizontal line on click:
    canvas.addEventListener('mousedown', event => {
      var rect = canvas.getBoundingClientRect();
      var x = event.clientX - rect.left | 0;
      var y = event.clientY - rect.top | 0;
    
      var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
      fill(x, y, imageData.data, imageData.width);
      context.putImageData(imageData, 0, 0);
    });
    
    // Load a sample image:
    var image = new Image();
    image.addEventListener('load', event => {
      context.drawImage(event.target, 0, 0, canvas.width, canvas.height);
    });
    image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAACCAIAAAABwbG5AAAAA3NCSVQICAjb4U/gAAAAGXRFWHRTb2Z0d2FyZQBnbm9tZS1zY3JlZW5zaG907wO/PgAAACZJREFUCJlj+I8Kbt68mZeXd/PmTbgIw38MsG/fvoKCgqdPn0K4ACAfPGrloJp1AAAAAElFTkSuQmCC';
    <canvas id="canvas"></canvas>

    您只需跟踪一个颜色通道,而不是将getIntensity函数的全部三个相加,即可提高性能。

    此外,由于混合模式,您需要先处理“清除”已填充区域,然后再使用其他颜色填充它。

    你可以。 G。将背景图像的单通道灰度图像数据阵列保留在内存中,并将其用作填充算法的源图像。

    (可手动或自动)将所有灰度图像转换为具有二进制边界的1位轮廓并将其用作填充算法的源,将结果平滑地与灰度图像混合可能更具性能。

答案 3 :(得分:0)

您对轮廓的检查过于严格,将浅灰色像素标记为无法着色的像素。我只是调整了您的阈值:

function matchOutlineColor (r,g,b,a,e) {
  return a >= 150;
}

注意我的白色值和alpha的数字要高得多。你可以进一步调整这个,但这对我来说很好。以下是一些前后照片:

之前(放大200%) enter image description here

之后(放大200%) enter image description here