画布 - 洪水填充在透明 PNG 图像的边缘留下白色像素

2024-01-12

现在,我尝试使用文章中的洪水填充算法执行洪水填充算法来填充透明PNG图像在洪水填充算法期间如何避免超过最大调用堆栈大小? https://stackoverflow.com/questions/59833738/how-can-i-avoid-exceeding-the-max-call-stack-size-during-a-flood-fill-algorithm它使用非递归方法和 Uint32Array 来处理颜色堆栈,工作得很好。

然而,这种洪水填充算法留下了未填充的白色(实际上是浅灰色边缘或抗锯齿边缘)。这是我的代码:

 var BrushColorString  = '#F3CDA6'; // skin color 
canvas.addEventListener('mousedown', function(e) {
        
        const rect = canvas.getBoundingClientRect()
        CanvasMouseX = e.clientX - rect.left;
        CanvasMouseY = e.clientY - rect.top;
        
        if (mode === 'flood-fill')
        {
            // test flood fill algorithm
            paintAt(context,  CanvasMouseX,CanvasMouseY,hexToRgb(BrushColorString));
            
        }
    });
function paintAt(ContextOutput,startX, startY,curColor) {
//function paintAt(ctx,startX, startY,curColor) {   
    // read the pixels in the canvas
    
    const width = ContextOutput.canvas.width, 
    height = ContextOutput.canvas.height,pixels = width*height;
    const imageData = ContextOutput.getImageData(0, 0, width, height);
    var data1 = imageData.data;
    const p32 = new Uint32Array(data1.buffer);  
    const stack = [startX + (startY * width)]; // add starting pos to stack
    const targetColor = p32[stack[0]];
    var SpanLeft = true, SpanRight = true; // logic for spanding left right
    var leftEdge = false, rightEdge = false; 
     
    // proper conversion of color to Uint32Array  
    const newColor = new Uint32Array((new Uint8ClampedArray([curColor.r,curColor.g, curColor.b, curColor.a])).buffer)[0];
    // need proper comparison of target color and new Color
    if (targetColor === newColor || targetColor === undefined) { return } // avoid endless loop
    

    while (stack.length){  
    
        let idx = stack.pop();
        while(idx >= width && p32[idx - width] === targetColor) { idx -= width }; // move to top edge
        SpanLeft = SpanRight = false;   // not going left right yet 
        leftEdge = (idx % width) === 0;          
        rightEdge = ((idx +1) % width) === 0;
        while (p32[idx] === targetColor) {
            p32[idx] = newColor;
            if(!leftEdge) {
                if (p32[idx - 1] === targetColor) { // check left
                    if (!SpanLeft) {        
                        stack.push(idx - 1);  // found new column to left
                        SpanLeft = true;  // 
                    } else if (SpanLeft) { 
                        SpanLeft = false; 
                    }
                }
            }
            if(!rightEdge) {
                if (p32[idx + 1] === targetColor) {
                    if (!SpanRight) {
                        stack.push(idx + 1); // new column to right
                        SpanRight = true;
                    }else if (SpanRight) { 
                        SpanRight = false; 
                    }
                }
            }
            idx += width;
        
        }
        
    
    } 
    
 
    clearCanvas(ContextOutput);
    ContextOutput.putImageData(imageData,0, 0); 
     
    
    
};
function hexToRgb(hex) {
        var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
        return result ? {
              r: parseInt(result[1], 16),
              g: parseInt(result[2], 16),
              b: parseInt(result[3], 16),
              a: 255
        } : null;
    }; 

到目前为止,我已尝试使用以下建议:

  1. 使用 matchOutlineColor 函数使用 RGBA 值中提到的画布 - 洪水填充在边缘留下白色像素 https://stackoverflow.com/questions/37679053/canvas-floodfill-leaves-white-pixels-at-edges/37836174
  2. 当我尝试实现“根据强度梯度变化而不是简单阈值限制填充区域”时提到画布 - 洪水填充在边缘留下白色像素 https://stackoverflow.com/questions/37679053/canvas-floodfill-leaves-white-pixels-at-edges/37836174这被认为是最有前途的算法,我仍然不知道如何以现有算法的最小变化来实现该算法,以处理透明图像情况下的抗锯齿边缘问题。
  3. 当我看一下如何应用公差和公差淡出中提到的示例时画布洪水填充未填充到边缘 https://stackoverflow.com/questions/41304168/canvas-flood-fill-not-filling-to-edge,我仍然不知道如何在我的情况下实现这样的宽容和宽容Fade。
  4. 色差法(colorDiff 函数)在上述公差范围内Canvas Javascript FloodFill 算法留下白色像素而没有颜色 https://stackoverflow.com/questions/62825533/canvas-javascript-floodfill-algorithm-left-white-pixels-without-color到目前为止仍然不起作用。类似的事情可以说是 colorMatch 函数在 Range Square (rangeSq) 中提到的如何使用 HTML Canvas 执行洪水填充? https://stackoverflow.com/questions/2106995/how-can-i-perform-flood-fill-with-html-canvas仍然无法解决抗锯齿边缘问题。

如果您对如何处理洪水填充算法的抗锯齿边缘问题有任何想法,请尽快回复。

Updated:

以下是根据建议修改后的 PaintAt 函数代码,其中考虑了容差:

<div id="container"><canvas id="control" >Does Not Support Canvas Element</canvas></div>
 <div><label for="tolerance">Tolerance</label>
<input id="tolerance" type="range" min="0" max="255" value="32" step="1" oninput="this.nextElementSibling.value = this.value"><output>32</output></div>
var canvas = document.getElementById("control");
var context = canvas.getContext('2d');
var CanvasMouseX =  -1; var CanvasMouseY = -1;
var BrushColorString  = '#F3CDA6'; // skin color

 
 canvas.addEventListener('mousedown', function(e) {
        
        const rect = canvas.getBoundingClientRect()
        CanvasMouseX = e.clientX - rect.left;
        CanvasMouseY = e.clientY - rect.top;
         
        // testing 
        
        
        if (mode === 'flood-fill')
        {
            // test flood fill algorithm
            paintAt(context,CanvasMouseX,CanvasMouseY,
hexToRgb(BrushColorString),tolerance.value);
             
            
        }
    });
function hexToRgb(hex) {
    var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    return result ? {
          r: parseInt(result[1], 16),
          g: parseInt(result[2], 16),
          b: parseInt(result[3], 16),
          a: 255
    } : null;
};
function clearCanvas(ctx) {
    ctx.clearRect(0, 0,ctx.canvas.width,ctx.canvas.height);
    
}; 
function colorDistance(index, R00,G00,B00,A00, data0)
{
    var index1 = index << 2; // multiplyed by 4
    const R = R00 - data0[index1 + 0];
    const G = G00 - data0[index1 + 1];
    const B = B00 - data0[index1 + 2];
    const A = A00 - data0[index1 + 3];      
    return Math.sqrt((R * R) + (B * B) + (G * G) + (A * A));
}

function paintAt(ContextOutput,startX, startY,curColor,tolerance) {
    // read the pixels in the canvas
    
    const width = ContextOutput.canvas.width, 
          height = ContextOutput.canvas.height, pixels = width*height;
        
    const rightEdgeNum = width - 1, bottomEdgeNum = height - 1; 
    const imageData = ContextOutput.getImageData(0, 0, width, height);
    var data1 = imageData.data;
    const p32 = new Uint32Array(data1.buffer);  
    const stack = [startX + (startY * width)]; // add starting pos to stack
    const targetColor = p32[stack[0]];
    
    var SpanLeft = true, SpanRight = true; // logic for spanning left right
    var leftEdge = false, rightEdge = false, IsBlend = false; 
    const DistancesArray = new Uint16Array(pixels);  // array distance value  
    var R=-1,G=-1,B=-1,A = -1,idx =0,Distance=0; 
    var R0 = data1[(4*(startX + (startY * width)))+0],
        G0 = data1[(4*(startX + (startY * width)))+1], 
        B0 = data1[(4*(startX + (startY * width)))+2],
        A0 = data1[(4*(startX + (startY * width)))+3];
    var CalculatedTolerance = Math.sqrt(tolerance * tolerance * 4);

    const BlendR = curColor.r |0, BlendG = curColor.g |0, 
          BlendB = curColor.b |0, BlendA = curColor.a|0; 
    // color variable for blending 
    const newColor = new Uint32Array((new Uint8ClampedArray([BlendR,BlendG,BlendB,BlendA])).buffer)[0];  

    if (targetColor === newColor || targetColor === undefined) { return } 
    // avoid endless loop
    
        while (stack.length){  
            idx = stack.pop();

            while (idx >= width && 
            colorDistance(idx - width,R0,G0,B0,A0,data1) <= CalculatedTolerance) { idx -= width }; // move to top edge
            SpanLeft = SpanRight = false;   // not going left right yet 
            leftEdge = (idx % width) === 0;          
            rightEdge = ((idx +1) % width) === 0;

            while ((Distance = colorDistance(idx,R0,G0,B0,A0,data1)) <= CalculatedTolerance) {
                DistancesArray[idx] = (Distance / CalculatedTolerance) * 255 | 0x8000; 
                p32[idx] = newColor; 
                if(!leftEdge) {

                    if (colorDistance(idx - 1,R0,G0,B0,A0,data1) <= CalculatedTolerance) { // check left
                        if (!SpanLeft) {        
                            stack.push(idx - 1);  // found new column to left
                            SpanLeft = true;  // 
                        } else if (SpanLeft) { 
                            SpanLeft = false; 
                        }
                    }
                }
                if(!rightEdge) {
                    if (colorDistance(idx + 1,R0,G0,B0,A0,data1) <= CalculatedTolerance) { 
                        if (!SpanRight) {
                        stack.push(idx + 1); // new column to right
                        SpanRight = true;
                        }else if (SpanRight) { 
                            SpanRight = false; 
                        }
                    }
                }
                idx += width;
    
            }
        }    
        idx = 0;
        while (idx <= pixels-1) {
            Distance = DistancesArray[idx];
            if (Distance !== 0) {
                if (Distance === 0x8000) {
                    p32[idx] = newColor;
                } else {
                     IsBlend = false;
                    const x = idx % width;
                    const y = idx / width | 0;
                    if (x >= 1 && DistancesArray[idx - 1] === 0) { IsBlend = true }
                    else if (x <= rightEdgeNum -1 && DistancesArray[idx + 1] === 0) { IsBlend = true }
                    else if (y >=1 && DistancesArray[idx - width] === 0) { IsBlend = true }
                    else if (y <=bottomEdgeNum-1 && DistancesArray[idx + width] === 0) { IsBlend = true }
                    if (IsBlend) {
                        // blending at the edge 
                        Distance &= 0xFF;             
                        Distance = Distance / 255;        
                        const invDist = 1 - Distance; 
                        const idx1 = idx << 2;    
                        data1[idx1 + 0] = data1[idx1 + 0] * Distance + BlendR * invDist;
                        data1[idx1 + 1] = data1[idx1 + 1] * Distance + BlendG * invDist;
                        data1[idx1 + 2] = data1[idx1 + 2] * Distance + BlendB * invDist;
                        data1[idx1 + 3] = data1[idx1 + 3] * Distance + BlendA * invDist;
                    
                    } else {
                        p32[idx] = newColor;
                    }
                }
            }
            idx++;
        }
    // this recursive algorithm works but still not working well due to the issue stack overflow!
    clearCanvas(ContextOutput);
    ContextOutput.putImageData(imageData,0, 0); 
   // way to deal with memory leak at the array. 
    DistancesArray = [];

    newColor = [];
    p32 = [];
    
};

However, the results of flood fill have been found wanting as shown in the transition tolerance as shown here:' Result at the tolerance point at the transition Result at the tolerance point at the transition when the tolerance has become too much

当宽容变得太多的时候,我该如何处理这种问题呢?任何替代算法将不胜感激。


第四维双通道洪水填充

我是已接受答案的作者在洪水填充算法期间如何避免超过最大调用堆栈大小? https://stackoverflow.com/questions/59833738/how-can-i-avoid-exceeding-the-max-call-stack-size-during-a-flood-fill-algorithm and 画布洪水填充未填充到边缘 https://stackoverflow.com/questions/41304168/canvas-flood-fill-not-filling-to-edge

不幸的是没有完美的解决方案。

下面的方法有问题。

  • 设置容差以使其获得所有边缘锯齿通常会填充不需要的区域。
  • 将容差设置得太低可能会使边缘看起来比标准填充更糟糕。
  • 重复填充将导致较硬的边缘锯齿。
  • 使用简单的混合函数。正确的混合函数可以在 W3C 上找到合成和混合级别“混合正常” https://drafts.fxtf.org/compositing-1/#blendingnormal抱歉,我没有时间来完成这个答案。
  • 不容易转换为渐变或图案填充。

有一个更好的解决方案,但它有 1000 多行长,并且仅代码不适合 32K 答案限制。

这个答案演练了如何使用容差和简单的边缘混合来更改函数以减少边缘锯齿。

Note

  • 答案中的各个片段可能有拼写错误或名称错误。有关正确的工作代码,请参阅底部的示例。

宽容

检测边缘的最简单方法是使用容差并填充填充原点处像素颜色容差内的像素。

这使得填充与锯齿边缘重叠,然后可以检测并混合锯齿边缘,以减少抗锯齿造成的伪影。

问题是,要获得良好的锯齿覆盖,需要很大的容差,这最终会填充您直观上不希望着色的区域。

计算颜色距离

颜色可以用红、绿、蓝 3 个值来表示。如果用 x、y、z 替换名称,就很容易看出每种颜色在 3D 空间中具有唯一的位置。

更好的是,这个 3D 空间中任意两种颜色之间的距离与感知的颜色差异直接相关。因此,我们可以使用简单的数学来计算差异(毕达哥拉斯)。

由于我们还需要考虑 Alpha 通道,因此我们需要提高一维。每种颜色及其 Alpha 部分在 4D 空间中都有一个独特的点。这些 4D 颜色之间的距离与颜色和透明度的感知差异直接相关。

幸运的是,我们不需要想象 4D 空间,我们所做的就是扩展数学(毕达哥拉斯适用于所有欧几里德维度)。

因此,我们获得了可以添加到洪水填充函数中的函数和准备代码。

var idx = stack[0] << 2; // remove let first line inside while (stack.length){ 
const r = data1[idx] ;
const g = data1[idx + 1] ;
const b = data1[idx + 2];
const a = data1[idx + 3]
function colorDist(idx) {  // returns the spacial distance from the target color of pixel at idx
    idx <<= 2;
    const R = r - data1[i];
    const G = g - data1[i + 1];
    const B = b - data1[i + 2];
    const A = a - data1[i + 3];      
    return (R * R + B * B + G * G + A * A) ** 0.5;
}

在函数声明中,我们添加一个参数容差,指定为 0 到 255 的值

函数声明从

function paintAt(contextOutput, startX, startY, curColor) {

To

function paintAt(contextOutput, startX, startY, curColor, tolerance = 0) {

With tolerance作为可选参数。

  • A tolerance0 只填充targetColor
  • A tolerance255 应填充所有像素

我们需要将容差从通道值转换为 4D 距离值,以便 255 覆盖 4D 颜色空间中两种颜色之间的最大距离。

将以下行添加到函数顶部paintAt

 tolerance = (tolerance * tolerance * 4) ** 0.5; // normalize to 4D RGBA space

我们现在需要更改像素匹配语句以使用容差。任何你有的地方p32[idx] === targetColor或类似的需要替换为colorDist(idx) <= tolerance。内部 while 循环是个例外,因为我们需要使用 4D 颜色距离

 while (checkPixel(ind)) {

becomes

 // declare variable dist at top of function
 while ((dist = colorDist(idx)) <= tolerance) {

双通道解决方案

为了消除锯齿,我们需要按与颜色距离成比例的量混合填充颜色。

对所有像素执行此操作意味着,如果颜色距离不为 0 且小于容差,远离填充边缘的像素将获得错误的颜色。

我们只想混合位于填充边缘的像素,不包括画布边缘的像素。对于许多像素,当我们遇到它们时,无法知道像素是否位于填充的边缘。我们只能知道何时找到所有填充的像素。

首先通过洪水填充

因此,我们必须保留一个数组来保存所有填充像素的颜色距离

在函数的顶部创建一个缓冲区来保存像素颜色距离。

const distances = new Uint16Array(width*height);

然后在内部循环中以及设置像素颜色设置匹配位置距离。

 while ((dist = colorDist(idx)) <= tolerance) {
     //Must not fill color here do in second pass p32[idx] = newColor;
     distances[idx] = (dist / tolerance) * 255 | 0x8000; 

为了跟踪哪些像素被填充,我们设置距离值的最高位。这意味着对于要填充的所有像素,距离将保持非零值,对于要忽略的像素,距离将保持零。这是通过| 0x8000

填充的主要部分还没有完成。在开始下一个通道之前,我们让填充完成它的工作。

第二遍边缘检测和混合

外循环退出后,我们一次跨过每个像素一个。检查是否需要填写。

如果需要填充,我们提取颜色距离。如果为零,则设置像素颜色p32大批。如果距离不为零,我们将检查其周围的 4 个像素。如果 4 个相邻像素中的任何一个被标记为不填充distances[idx] === 0并且该像素不在画布边界之外,我们知道它是边缘并且需要混合。

// declare at top of function
var blend, dist, rr, gg, bb, aa;

// need fill color's channels for quickest possible access.
const fr = curColor.r | 0;
const fg = curColor.g | 0;
const fb = curColor.b | 0;
const fa = curColor.a | 0;


// after main fill loop.
idx = 0;
const rightEdge = width - 1, bottomEdge = height - 1; 
while (idx < width * height){
    dist = distances[idx];
    if (dist !== 0) {
        if (dist === 0x8000) {
            p32[idx] = newColor;
        } else {
            blend = false;
            const x = idx % width;
            const y = idx / width | 0;
            if (x > 0 && distances[idx - 1] === 0) { blend = true }
            else if (x < rightEdge && distances[idx + 1] === 0) { blend = true }
            else if (y > 0 && distances[idx - width] === 0) { blend = true }
            else if (y < bottomEdge && distances[idx + width] === 0) { blend = true }

            if (blend) { // pixels is at fill edge an needs to blend
                dist &= 0xFF;             // remove fill bit
                dist = dist / 255;        // normalize to range 0-1
                const invDist = 1 - dist; // invert distance

                // get index in byte array
                const idx1 = idx << 2;    // same as idx * 4 
                
                // simple blend function (not the same as used by 2D API)
                data[idx1]     = data[idx1    ] * dist + fr * invDist;
                data[idx1 + 1] = data[idx1 + 1] * dist + fg * invDist;
                data[idx1 + 2] = data[idx1 + 2] * dist + fb * invDist;
                data[idx1 + 3] = data[idx1 + 3] * dist + fa * invDist;

            } else { 
                p32[idx] = newColor;
            }
       }
    }
    idx++;
}

现在只需将新的像素数组放到画布上即可。

Example

此示例是代码修改版本的基本包装。它是为了确保我没有犯任何算法错误,并强调使用此方法时的质量或质量缺陷。

  • 单击第一个按钮添加随机圆圈。
  • 使用滑块设置容差 0 - 255
  • 单击“清除”以清除画布。
  • 单击画布在鼠标位置填充随机颜色。

画布已缩放 2 倍以使工件更加可见。

功能floodFill取代你的paintAt太大,应分为两部分,一部分用于填充通道,另一部分用于边缘检测和混合。

const ctx = canvas.getContext("2d");
var circle = true;
test();
canvas.addEventListener("click", e => {circle = false; test(e)});
toggleFill.addEventListener("click",e => {circle = true; test(e)});
clear.addEventListener("click",()=>ctx.clearRect(0,0,500,500));
function randomCircle() {
    ctx.beginPath();
    ctx.strokeStyle = "black";
    ctx.lineWidth = 4;
    const x = Math.random() * 100 | 0;
    const y = Math.random() * 100 | 0;
    ctx.arc(x, y, Math.random() * 25 + 25, 0 , Math.PI * 2);
    ctx.stroke();
    return {x,y};
}

function test(e) {
    if (circle) {
        toggleFill.textContent = "Click canvas to fill";
        randomCircle();
    } else {
        toggleFill.textContent = "Click button add random circle";
        const col = {
            r: Math.random() * 255 | 0,
            g: Math.random() * 255 | 0,
            b: Math.random() * 255 | 0,
            a: Math.random() * 255 | 0,
        };
        floodFill(ctx, (event.offsetX - 1) / 2 | 0, (event.offsetY -1) / 2| 0, col, tolerance.value);
    }
}

// Original function from SO question https://stackoverflow.com/q/65359146/3877726
function floodFill(ctx, startX, startY, curColor, tolerance = 0) {
    var idx, blend, dist, rr, gg, bb, aa, spanLeft = true, spanRight = true, leftEdge = false, rightEdge = false;
    const width = ctx.canvas.width,  height = ctx.canvas.height, pixels = width*height;
    const imageData = ctx.getImageData(0, 0, width, height);
    const data = imageData.data;
    const p32 = new Uint32Array(data.buffer);  
    const stack = [startX + (startY * width)]; 
    const targetColor = p32[stack[0]];
    const fr = curColor.r | 0;
    const fg = curColor.g | 0;
    const fb = curColor.b | 0;
    const fa = curColor.a | 0;  
    const newColor = (fa << 24) + (fb << 16) + (fg << 8) + fr;     
    if (targetColor === newColor || targetColor === undefined) { return } 

    idx = stack[0] << 2; 
    const rightE = width - 1, bottomE = height - 1; 
    const distances = new Uint16Array(width*height);   
    tolerance = (tolerance * tolerance * 4) ** 0.5; 
  
    const r = data[idx] ;
    const g = data[idx + 1] ;
    const b = data[idx + 2];
    const a = data[idx + 3]
    function colorDist(idx) {  
        if (distances[idx]) { return Infinity }
        idx <<= 2;
        const R = r - data[idx];
        const G = g - data[idx + 1];
        const B = b - data[idx + 2];
        const A = a - data[idx + 3];      
        return (R * R + B * B + G * G + A * A) ** 0.5;
    }

    while (stack.length) {  
        idx = stack.pop();
        while (idx >= width && colorDist(idx - width) <= tolerance) { idx -= width }; // move to top edge
        spanLeft = spanRight = false;   // not going left right yet 
        leftEdge = (idx % width) === 0;          
        rightEdge = ((idx + 1) % width) === 0;
        while ((dist = colorDist(idx)) <= tolerance) {
            distances[idx] = (dist / tolerance) * 255 | 0x8000; 
            if (!leftEdge) {
                if (colorDist(idx - 1) <= tolerance) { 
                    if (!spanLeft) {        
                        stack.push(idx - 1); 
                        spanLeft = true;   
                    } else if (spanLeft) { 
                        spanLeft = false; 
                    }
                }
            }
            if (!rightEdge) {
                if (colorDist(idx + 1) <= tolerance) {
                    if (!spanRight) {
                        stack.push(idx + 1); 
                        spanRight = true;
                    }else if (spanRight) { 
                        spanRight = false; 
                    }
                }
            }
            idx += width;
        }
    } 
    idx = 0;
    while (idx < pixels) {
        dist = distances[idx];
        if (dist !== 0) {
            if (dist === 0x8000) {
                p32[idx] = newColor;
            } else {
                blend = false;
                const x = idx % width;
                const y = idx / width | 0;
                if (x > 0 && distances[idx - 1] === 0) { blend = true }
                else if (x < rightE && distances[idx + 1] === 0) { blend = true }
                else if (y > 0 && distances[idx - width] === 0) { blend = true }
                else if (y < bottomE && distances[idx + width] === 0) { blend = true }
                if (blend) {
                    dist &= 0xFF;             
                    dist = dist / 255;        
                    const invDist = 1 - dist; 
                    const idx1 = idx << 2;    
                    data[idx1]     = data[idx1    ] * dist + fr * invDist;
                    data[idx1 + 1] = data[idx1 + 1] * dist + fg * invDist;
                    data[idx1 + 2] = data[idx1 + 2] * dist + fb * invDist;
                    data[idx1 + 3] = data[idx1 + 3] * dist + fa * invDist;
                } else { 
                    p32[idx] = newColor;
                }
            }
        }
        idx++;
    }

    ctx.putImageData(imageData,0, 0); 
}
canvas {
  width: 200px;
  height: 200px;  
  border: 1px solid black;
}
<label for="tolerance">Tolerance</label>
<input id="tolerance" type="range" min="0" max="255" value="32" step="1"></input>
<button id ="toggleFill" >Click add random circle</button>
<button id ="clear" >Clear</button><br>
<canvas id="canvas" width="100" height="100"></canvas>
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

画布 - 洪水填充在透明 PNG 图像的边缘留下白色像素 的相关文章

  • 在数组/对象中查找项目的最快方法

    在 Javascript AS3 中查找列表或对象中项目的索引的最快方法是什么 我对这两种语言都提出这个要求 因为这两种语言的语法相似 假设 myArray one two three myObject one 1 two 2 three
  • 邮件附件媒体类型错误 Gmail API

    我正在尝试通过 Javascript 客户端中的 Gmail API 发送带有附加 jpeg 文件的消息 到目前为止我写的代码如下 ajax type POST url https www googleapis com upload gma
  • browserify 错误 /usr/bin/env: 节点: 没有这样的文件或目录

    我通过 apt get install 安装了 node js 和 npm 以及所有依赖项 然后安装了 browserify npm install browserify g 它完成了整个过程 看起来安装正确 但是当我尝试为此做一个简单的捆
  • 如何制作像Stackoverflow一样的可折叠评论框

    我正在构建一个网站 并且有一个状态更新列表 我希望允许用户为列表中的每个项目撰写评论 但是我正在尝试实现一个类似于堆栈溢出工作方式的用户界面 特别是可折叠的评论表单 列表 用户在其中单击对列表中的特定状态更新添加评论 并且在列表中的该项目下
  • 如何从 URL 字符串中删除某些参数?

    我有这个var存储表示充满参数的 URL 的字符串 我正在使用 AngularJS 我不确定是否有任何有用的模块 或者可能使用纯 JavaScript 来删除不需要的 URL 参数而无需使用正则表达式 例如我需要删除 month 05并且
  • 检测 iframe 内容加载失败

    我可以使用以下命令检测 iframe 的内容何时加载load事件 不幸的是 就我的目的而言 这有两个问题 如果加载页面时出现错误 404 500 等 则永远不会触发加载事件 如果某些图像或其他依赖项加载失败 则会照常触发加载事件 有什么方法
  • 使用 easyXDM 调整 IFrame 大小

    我将 iFrame 代码提供给客户 以便他们可以显示我网站上的动态内容 我希望其页面上的 iFrame 能够调整大小以适合我的内容 我按照 easyXDM 网站的说明进行操作 但也许我遗漏了一些东西 我没有收到任何错误 但 iFrame 保
  • JointJS - 处理链接删除点击

    创建链接后 将鼠标悬停在其上会显示红色 X 以将其删除 单击此按钮将触发一系列事件 通过订阅 全部 活动收集 单元格 向下指针 链接 向下指针 cell pointermove x5 似乎可疑 单元格 指针向上 在浏览了文档并花费了太长时间
  • django ajax post 403被禁止

    使用 django 1 4 当我尝试从我的 javascript 做我的 django 服务器上的帖子时 我收到 403 错误 我的 get 工作正常 尽管问题仅出在帖子上 也尝试过 csrf exempt但没有运气 更新 我现在可以发布我
  • 禁用任何类型的浏览器窗口滚动?

    有没有办法禁用滚动 不仅仅是滚动条 还有浏览器窗口的全部功能 根据您对 Keit 的回答 您不想在打开灯箱时滚动处于活动状态 如果是这种情况 您可以使用以下 css 在打开灯箱的同时向正文添加一个类 这个解决方案的好处是它保留了滚动 空间
  • 如何检查jquery数据表中的每个复选框?

    我有一个第一列带有复选框的表格 我使用 jQuery DataTable 插件显示我的表格 我制作了 2 个链接来选择 取消选择每个复选框 这是选择全部的一个 a href Select all a 和 JavaScript functio
  • Web浏览器控件:如何捕获文档事件?

    我正在使用 WPF 的 WebBrowser 控件加载一个简单的网页 在这个页面上我有一个锚点或一个按钮 我想在我的应用程序后面的代码中 即在 C 中 捕获该按钮的单击事件 WebBrowser 控件是否有办法捕获加载页面元素上的单击事件
  • 响应式菜单:悬停子菜单显示错误

    简而言之 我根据教程创建了一个响应式菜单 当您将鼠标悬停在投资组合按钮上时 菜单应该显示子菜单 而在移动模式下 您需要按该按钮才能显示子菜单 效果很好 问题是该教程有一个错误 如果您在桌面模式下按组合按钮 子菜单将不会再次显示 除非您按 单
  • 地址更改时如何停止 Angular 重新加载

    我正在使用 Angular 的scrollTo and anchorScroll像这样 app controller TestCtrl function scope location anchorScroll scope scrollTo
  • Chrome Prerender 功能每次都会被取消

    我正在尝试 Chrome 中的预渲染功能 但是当我检查网络时 我可以看到任何链接的请求都被取消 我使用以下语法 我尝试了现场演示http prerender test appspot com http prerender test apps
  • 如何在 jQuery 中检查复选框是否被选中?

    我需要检查checked复选框的属性 并使用 jQuery 根据选中的属性执行操作 例如 如果age复选框被选中 然后我需要显示一个文本框来输入age 否则隐藏文本框 但下面的代码返回false默认情况下 if isAgeSelected
  • 如何仅在第一次访问时弹出模态窗口

    我有一个模式窗口 当您访问某个页面时会弹出 访客必须选择我同意或我不同意 我需要一个漂亮的小 jquery 脚本 它会记住谁之前访问过该页面并同意 这样他们每次访问该页面时就不会弹出模式 有人可以推荐一个好的脚本来使用吗 这是代码 div
  • jQuery fadeOut 一个 div,fadeIn 另一个 div 在其位置

    我正在尝试一个简单的 jQuery 脚本来淡出一个 div 并淡入另一个 div 但由于某种原因 第一个 div 永远不会淡出 这可能是代码的一个明显问题 但我似乎无法弄清楚 div div div div
  • 我可以防止将 Leaflet 地图平移到世界边缘之外吗?

    有没有办法限制平移到世界边缘之外 在这幅画中 棕色是世界 灰色是虚空 我想让它不可能像这样平移 Leaflet 允许您控制地图抵抗被拖出边界的程度maxBoundsViscosity选项 值 0 到 1 将其设置为最大值会完全禁用拖动出界
  • html5 canvas 使用图像作为蒙版

    是否可以使用具有形状的图像作为整个画布或画布内图像的蒙版 我想将图像放置在画布中 并在图像上添加蒙版 然后将其另存为新图像 您可以使用 source in globalCompositeOperation 将黑白图像用作蒙版 首先 将蒙版图

随机推荐