javascript - Canvas - floodfill 在边缘留下白色像素

标签 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>

最佳答案

我建议两个改变:

  1. 混合像素和填充颜色而不是硬覆盖
  2. 根据强度梯度变化而不是简单的阈值来限制填充区域

在水平和垂直方向上填充直到强度梯度的符号从 + 翻转到 - 或 - 到 + 让我们填充整个区域,包括黑色边框的“一半”。通过检查梯度,我们只是确保不超过强度最小值,从而避免填充相邻区域。

看看下面的演示:

// 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>

您可以通过仅跟踪一个颜色 channel 而不是为您的 getIntensity 函数对所有三个颜色 channel 求和来提高性能。

此外,由于混合模式的原因,您需要在用另一种颜色再次填充之前“清除”一个已经填充的区域。

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

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

关于javascript - Canvas - floodfill 在边缘留下白色像素,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37679053/

相关文章:

javascript - 桌面动量风格滚动和视差

javascript - 当我的代码运行时出现 'Rich Embed fields may not be empty' 错误

javascript - 图像作为 html Canvas 的模板

javascript - 错误-使用Javascript和HTML Canvas 制作鼠标事件绘制功能

javascript - 如何获得新的 Canvas 弧线笔划样式?

css - iMac 上 Mavericks 下的 Safari 7.0.2 略微改变了网页颜色

perl - 如何设置 Gtk2::Button 的文本颜色?

javascript - 如何使用 XMLHttpRequest 捕获无效 URL 引发的错误

Python OpenCV : Why does fillPoly() only draw grey polygons, 无论其颜色参数如何?

javascript - 创建 map 时如何隐藏侧面板和添加数据弹出窗口?