javascript - कैनवास बाढ़ को किनारे पर भरने से भरना नहीं है



html5 canvas (1)

मास्क बनाने के लिए बाढ़ भर का उपयोग करें

मैं बस एक बाढ़ के लिए हुआ था दूसरे दिन भरें जो एंटीअलियाज किनारों की समस्या को हल करता है

सीधे कैनवास पर पेंट करने के बजाय, मैं एक बाइट सरणी के लिए पेंट करता हूं जिसका उपयोग मुखौटा बनाने के लिए किया जाता है। मुखौटा अल्फा मूल्यों को सेट करने की अनुमति देता है।

भरने में tolerance और toleranceFade यह नियंत्रित होता है कि यह कैसे रंगों के साथ सौदा करता है जो सहिष्णुता के मूल्य पर आते हैं।

जब शुरू रंग और सहिष्णुता के बीच पिक्सेल का अंतर (tolerance - toleranceFade) से अधिक होता है, तो मैं उस पिक्सेल के लिए 255 - ((differance - (tolerance - toleranceFade)) / toleranceFade) * 255 से अल्फा सेट करता हूं 255 - ((differance - (tolerance - toleranceFade)) / toleranceFade) * 255 जिसमें एक अच्छा चिकनी मिश्रण होता है लाइनों के किनारों यद्यपि यह उच्च स्थितियों के लिए सभी स्थितियों के लिए काम नहीं करता है, यह एक प्रभावी समाधान है।

नीचे दिए गए उदाहरण toleranceFade के साथ और बिना परिणाम दिखाते हैं। नीली toleranceFade बिना है, toleranceFade , लाल tolerance साथ 190 पर सेट है और toleranceFade 90 की फ़ेड।

आपको अपनी ज़रूरतों के लिए सर्वोत्तम परिणाम प्राप्त करने के लिए सेटिंग के साथ खेलना होगा।

function showExample(){
    var canvas = document.createElement("canvas");
    canvas.width = 200;
    canvas.height = 200;
    var ctx = canvas.getContext("2d");
    document.body.appendChild(canvas);
    ctx.fillStyle = "white"
    ctx.fillRect(0,0,canvas.width,canvas.height)
    ctx.lineWidth = 4;
    ctx.strokeStyle = "black"
    ctx.beginPath();
    ctx.arc(100,100,90,0,Math.PI * 2);
    ctx.arc(120,100,60,0,Math.PI * 2);
    ctx.stroke();
    ctx.fillStyle = "blue";
    floodFill.fill(100,100,1,ctx)
    ctx.fillStyle = "red";
    floodFill.fill(40,100,190,ctx,null,null,90)
}

// FloodFill2D from https://github.com/blindman67/FloodFill2D
var floodFill = (function(){
    "use strict";
    const extent = {
        top : 0,
        left : 0,
        bottom : 0,
        right : 0,        
    }
    var keepMask = false; // if true then a mask of the filled area is returned as a canvas image
    var extentOnly = false;  // if true then the extent of the fill is returned
    var copyPixels = false; // if true then creating a copy of filled pixels
    var cutPixels = false;  // if true and copyPixels true then filled pixels are removed
    var useBoundingColor = false; // Set the colour to fill up to. Will not fill over this colour
    var useCompareColor = false; // Rather than get the pixel at posX,posY use the compareColours
    var red, green, blue, alpha; // compare colours if 
    var canvas,ctx;    
    function floodFill (posX, posY, tolerance, context2D, diagonal, area, toleranceFade) {
        var w, h, painted, x, y, ind, sr, sg, sb, sa,imgData, data, data32, RGBA32, stack, stackPos, lookLeft, lookRight, i, colImgDat, differance, checkColour;
        toleranceFade = toleranceFade !== undefined && toleranceFade !== null ? toleranceFade : 0;
        diagonal = diagonal !== undefined && diagonal !== null ? diagonal : false;
        area = area !== undefined && area !== null ? area : {};
        area.x = area.x !== undefined ? area.x : 0;
        area.y = area.y !== undefined ? area.y : 0;
        area.w = area.w !== undefined ? area.w : context2D.canvas.width - area.x;
        area.h = area.h !== undefined ? area.h : context2D.canvas.height - area.y;
        // vet area is on the canvas.
        if(area.x < 0){
            area.w = area.x + area.w;
            area.x = 0;
        }
        if(area.y < 0){
            area.h = area.y + area.h;
            area.y = 0;
        }
        if(area.x >= context2D.canvas.width || area.y >= context2D.canvas.height){
            return false;
        }
        if(area.x + area.w > context2D.canvas.width){
            area.w = context2D.canvas.width - area.x;
        }
        if(area.y + area.h > context2D.canvas.height){
            area.h = context2D.canvas.height - area.y;
        }
        if(area.w <= 0 || area.h <= 0){
            return false;
        }    
        w = area.w;   // width and height
        h = area.h;
        x = posX - area.x;   
        y = posY - area.y;    
        if(extentOnly){
            extent.left = x; // set up extent
            extent.right = x;
            extent.top = y;
            extent.bottom = y;
        }
        
        if(x < 0 || y < 0 || x >= w || y >= h){
            return false;  // fill start outside area. Don't do anything
        }
        if(tolerance === 255 && toleranceFade === 0 && ! keepMask){  // fill all 
            if(extentOnly){
                extent.left = area.x; // set up extent
                extent.right = area.x + w;
                extent.top = area.y;
                extent.bottom = area.y + h;
            }
            context2D.fillRect(area.x,area.y,w,h);
            return true;
        }
        if(toleranceFade > 0){   // add one if on to get correct number of steps
            toleranceFade += 1;
        }


        imgData = context2D.getImageData(area.x,area.y,area.w,area.h);
        data = imgData.data; // image data to fill;
        data32 = new Uint32Array(data.buffer);
        painted = new Uint8ClampedArray(w*h);  // byte array to mark painted area;
        function checkColourAll(ind){
            if( ind < 0 || painted[ind] > 0){  // test bounds
                return false;
            }
            var ind4 = ind << 2;  // get index of pixel           
            if((differance = Math.max(        // get the max channel difference;
                Math.abs(sr - data[ind4++]),
                Math.abs(sg - data[ind4++]),
                Math.abs(sb - data[ind4++]),                
                Math.abs(sa - data[ind4++])
                )) > tolerance){    
                return false;
            }        
            return true
        }         
        // check to bounding colour
        function checkColourBound(ind){
            if( ind < 0 || painted[ind] > 0){  // test bounds
                return false;
            }
            var ind4 = ind << 2;  // get index of pixel
            differance = 0;
            if(sr === data[ind4] && sg === data[ind4 + 1] && sb === data[ind4 + 2] && sa === data[ind4 + 3]){
                return false
            }
            return true
        }         
        // this function checks the colour of only selected channels
        function checkColourLimited(ind){ // check only colour channels that are not null
            var dr,dg,db,da;
            if( ind < 0 || painted[ind] > 0){  // test bounds
                return false;
            }
            var ind4 = ind << 2;  // get index of pixel
            dr = dg = db = da = 0;
            if(sr !== null && (dr = Math.abs(sr - data[ind4])) > tolerance){
                return false;
            }
            if(sg !== null && (dg = Math.abs(sg - data[ind4 + 1])) > tolerance){
                return false;
            }
            if(sb !== null && (db = Math.abs(sb - data[ind4 + 2])) > tolerance){
                return false;
            }
            if(sa !== null && (da = Math.abs(sa - data[ind4 + 3])) > tolerance){
                return false;
            }
            diferance = Math.max(dr, dg, db, da);
            return true
        }         
        // set which function to check colour with
        checkColour = checkColourAll;
        if(useBoundingColor){
            sr = red;
            sg = green;
            sb = blue;
            if(alpha === null){
                ind = (y * w + x) << 2;  // get the starting pixel index
                sa = data[ind + 3];                     
            }else{
                sa = alpha;            
            }
            checkColour = checkColourBound;
            useBoundingColor = false;
        }else if(useCompareColor){
            sr = red;
            sg = green;
            sb = blue;
            sa = alpha;
            if(red === null || blue === null || green === null || alpha === null){
                checkColour = checkColourLimited;
            }
            useCompareColor = false;            
        }else{
            ind = (y * w + x) << 2;  // get the starting pixel index
            sr = data[ind];        // get the start colour that we will use tolerance against.
            sg = data[ind + 1];
            sb = data[ind + 2];
            sa = data[ind + 3];     
        }
        stack = [];          // paint stack to find new pixels to paint
        lookLeft = false;    // test directions
        lookRight = false;

        stackPos = 0;
        stack[stackPos++] = x;
        stack[stackPos++] = y;
        while (stackPos > 0) {   // do while pixels on the stack
            y = stack[--stackPos];  // get the pixel y
            x = stack[--stackPos];  // get the pixel x
            ind = x + y * w;
            while (checkColour(ind - w)) {  // find the top most pixel within tollerance;
                y -= 1;
                ind -= w;
            }
            //checkTop left and right if allowing diagonal painting
            if(diagonal && y > 0){
                if(x > 0 && !checkColour(ind - 1) && checkColour(ind - w - 1)){
                    stack[stackPos++] = x - 1;
                    stack[stackPos++] = y - 1;
                }
                if(x < w - 1 && !checkColour(ind + 1) && checkColour(ind - w + 1)){
                    stack[stackPos++] = x + 1;
                    stack[stackPos++] = y - 1;
                }
            }
            lookLeft = false;  // set look directions
            lookRight = false; // only look is a pixel left or right was blocked
            while (checkColour(ind) && y < h) { // move down till no more room
                if(toleranceFade > 0 && differance >= tolerance-toleranceFade){
                    painted[ind] = 255 - (((differance - (tolerance - toleranceFade)) / toleranceFade) * 255);
                    painted[ind] = painted[ind] === 0 ? 1 : painted[ind]; // min value must be 1
                }else{
                    painted[ind] = 255; 
                }
                if(extentOnly){
                    extent.left   = x < extent.left   ? x : extent.left;    // Faster than using Math.min
                    extent.right  = x > extent.right  ? x : extent.right;   // Faster than using Math.min
                    extent.top    = y < extent.top    ? y : extent.top;     // Faster than using Math.max
                    extent.bottom = y > extent.bottom ? y : extent.bottom;  // Faster than using Math.max
                }
                if (checkColour(ind - 1) && x > 0) {  // check left is blocked
                    if (!lookLeft) {        
                        stack[stackPos++] = x - 1;
                        stack[stackPos++] = y;
                        lookLeft = true;
                    }
                } else if (lookLeft) {
                    lookLeft = false;
                }
                if (checkColour(ind + 1) && x < w -1) {  // check right is blocked
                    if (!lookRight) {
                        stack[stackPos++] = x + 1;
                        stack[stackPos++] = y;
                        lookRight = true;
                    }
                } else if (lookRight) {
                    lookRight = false;
                }
                y += 1;                 // move down one pixel
                ind += w;
            }
            if(diagonal && y < h){  // check for diagonal areas and push them to be painted 
                if(checkColour(ind - 1) && !lookLeft && x > 0){
                    stack[stackPos++] = x - 1;
                    stack[stackPos++] = y;
                }
                if(checkColour(ind + 1) && !lookRight && x < w - 1){
                    stack[stackPos++] = x + 1;
                    stack[stackPos++] = y;
                }
            }
        }
        if(extentOnly){
            extent.top    += area.y;
            extent.bottom += area.y;
            extent.left   += area.x;
            extent.right  += area.x;
            return true;
        }
        canvas = document.createElement("canvas");
        canvas.width = w;
        canvas.height = h;
        ctx = canvas.getContext("2d");
        ctx.fillStyle = context2D.fillStyle;
        ctx.fillRect(0, 0, w, h);
        colImgDat = ctx.getImageData(0, 0, w, h);
        if(copyPixels){
            i = 0;
            ind = 0;
            if(cutPixels){
                while(i < painted.length){
                    if(painted[i] > 0){
                        colImgDat.data[ind] = data[ind];
                        colImgDat.data[ind + 1] = data[ind + 1];
                        colImgDat.data[ind + 2] = data[ind + 2];
                        colImgDat.data[ind + 3] = data[ind + 3] * (painted[i] / 255);
                        data[ind + 3] = 255 - painted[i];
                    }else{
                        colImgDat.data[ind + 3] = 0;
                        
                    }
                    i ++;
                    ind += 4;
                }
                context2D.putImageData(imgData, area.x, area.y);
            }else{
                while(i < painted.length){
                    if(painted[i] > 0){
                        colImgDat.data[ind] = data[ind];
                        colImgDat.data[ind + 1] = data[ind + 1];
                        colImgDat.data[ind + 2] = data[ind + 2];
                        colImgDat.data[ind + 3] = data[ind + 3] * (painted[i] / 255);
                    }else{
                        colImgDat.data[ind + 3] = 0;
                    }
                    i ++;
                    ind += 4;
                }
            }
            ctx.putImageData(colImgDat,0,0); 
            return true;            
            
        }else{
            i = 0;
            ind = 3;
            while(i < painted.length){
                colImgDat.data[ind] = painted[i];
                i ++;
                ind += 4;
            }
            ctx.putImageData(colImgDat,0,0);
        }
        if(! keepMask){
            context2D.drawImage(canvas,area.x,area.y,w,h);
        }
        return true;
    }
    
    return {
        fill : function(posX, posY, tolerance, context2D, diagonal, area, toleranceFade){
            floodFill(posX, posY, tolerance, context2D, diagonal, area, toleranceFade);
            ctx = undefined;
            canvas = undefined;
        },
        getMask : function(posX, posY, tolerance, context2D, diagonal, area, toleranceFade){
            keepMask = true;
            floodFill(posX, posY, tolerance, context2D, diagonal, area, toleranceFade);
            ctx = undefined;
            keepMask = false;
            return canvas;
        },
        getExtent : function(posX, posY, tolerance, context2D, diagonal, area, toleranceFade){
            extentOnly = true;
            if(floodFill(posX, posY, tolerance, context2D, diagonal, area, toleranceFade)){
                extentOnly = false;
                return {
                    top : extent.top,
                    left : extent.left,
                    right : extent.right,
                    bottom : extent.bottom,
                    width : extent.right - extent.left,
                    height : extent.bottom - extent.top,
                }
            }
            extentOnly = false;
            return null;
        },
        cut : function(posX, posY, tolerance, context2D, diagonal, area, toleranceFade){
            cutPixels = true;
            copyPixels = true;
            floodFill(posX, posY, tolerance, context2D, diagonal, area, toleranceFade);
            cutPixels = false;
            copyPixels = false;
            ctx = undefined;
            return canvas;
        },
        copy : function(posX, posY, tolerance, context2D, diagonal, area, toleranceFade){
            cutPixels = false;
            copyPixels = true;
            floodFill(posX, posY, tolerance, context2D, diagonal, area, toleranceFade);
            copyPixels = false;
            ctx = undefined;
            return canvas;            
        },
        setCompareValues : function(R,G,B,A){
            if(R === null && G === null && B === null && A === null){
                return;
            }
            red = R;
            green = G;
            blue = B;
            alpha = A;
            useBoundingColor = false;
            useCompareColor = true;
        },
        setBoundingColor : function(R,G,B,A){
            red = R;
            green = G;
            blue = B;
            alpha = A;
            useCompareColor = false;
            useBoundingColor = true;
        }
    }
}());


showExample();
Red floodFill.fill(40,100,190,ctx,null,null,90) tolerance 190, tolerance fade 90<br>Blue floodFill.fill(100,100,1,ctx) tolerance 1.<br>

अधिक जानकारी के लिए गिटौब फ्लडफिल 2 डी में रीडमी देखें

कैनवास पर खींची गई हलें भरने के लिए मैं एक बाढ़ भर एल्गोरिथ्म का उपयोग कर रहा हूं। मुझे जो समस्या है वह यह है कि एल्गोरिथ्म सर्कल के किनारे तक सही नहीं भर रहा है।

इस ब्लॉग पोस्ट पर आधारित एल्गोरिदम यहां है:

function paintLocation(startX, startY, r, g, b) {
    var colorLayer = context1.getImageData(0, 0, canvasWidth, canvasHeight);
    pixelPos = (startY * canvasWidth + startX) * 4;

    startR = colorLayer.data[pixelPos];
    startG = colorLayer.data[pixelPos + 1];
    startB = colorLayer.data[pixelPos + 2];

    var pixelStack = [
        [startX, startY]
    ];

    var drawingBoundTop = 0;
    while (pixelStack.length) {
        var newPos, x, y, pixelPos, reachLeft, reachRight;
        newPos = pixelStack.pop();
        x = newPos[0];
        y = newPos[1];

        pixelPos = (y * canvasWidth + x) * 4;
        while (y-- >= drawingBoundTop && matchStartColor(colorLayer, pixelPos, startR, startG, startB)) {
            pixelPos -= canvasWidth * 4;
        }
        pixelPos += canvasWidth * 4;
        ++y;
        reachLeft = false;
        reachRight = false;
        while (y++ < canvasHeight - 1 && matchStartColor(colorLayer, pixelPos, startR, startG, startB)) {
            colorPixel(colorLayer, pixelPos, r, g, b);

            if (x > 0) {
                if (matchStartColor(colorLayer, pixelPos - 4, startR, startG, startB)) {
                    if (!reachLeft) {
                        pixelStack.push([x - 1, y]);
                        reachLeft = true;
                    }
                } else if (reachLeft) {
                    reachLeft = false;
                }
            }

            if (x < canvasWidth - 1) {
                if (matchStartColor(colorLayer, pixelPos + 4, startR, startG, startB)) {
                    if (!reachRight) {
                        pixelStack.push([x + 1, y]);
                        reachRight = true;
                    }
                } else if (reachRight) {
                    reachRight = false;
                }
            }

            pixelPos += canvasWidth * 4;
        }
    }
    context1.putImageData(colorLayer, 0, 0);
}

कृपया मुझे देखने के लिए JSFiddle या नीचे की छवि देखें। किसी भी सर्कल के अंदर क्लिक करने से पीले और काले रंग के बीच रंग बदल जाएगा (समस्या काले के साथ कहीं अधिक दिखाई दे रही है)

मैंने पढ़ा है कि यह समस्या एंटी- context1.imageSmoothingEnabled = true; साथ कुछ हो सकती है और मैंने इसे context1.imageSmoothingEnabled = true; साथ बंद करने की कोशिश की context1.imageSmoothingEnabled = true; लेकिन इससे कोई फर्क नहीं पड़ा।

इस सवाल के मुताबिक मैंने अपने मैचस्टार्ट कॉलेयर फ़ंक्शन को बदलने की भी कोशिश की है, लेकिन इससे मदद नहीं मिलती

function matchStartColor(colorLayer, pixelPos, startR, startG, startB) {
    var r = colorLayer.data[pixelPos];
    var g = colorLayer.data[pixelPos + 1];
    var b = colorLayer.data[pixelPos + 2];

    return (r == startR && g == startG && b == startB);
}

मुझे लगता है कि इस तथ्य के साथ ऐसा कुछ हो सकता है कि हलकों का कोई रंग भरना न हो और कैनवास की पृष्ठभूमि सफेद न हो लेकिन यह पारदर्शी काले रंग का है। मैंने कैनवास की पृष्ठभूमि को सफेद करने के लिए बदलने की कोशिश की है लेकिन इससे भी मदद नहीं मिली





flood-fill