【问题标题】:Calculate the number of pixels inside a shape drawn on HTML5 canvas?计算在 HTML5 画布上绘制的形状内的像素数?
【发布时间】:2013-03-14 06:22:51
【问题描述】:

我正在使用sketch.js 在 HTML5 画布上动态绘制非常简单的形状。

有没有办法计算画布上绘制的封闭形状内的像素数?

【问题讨论】:

  • 不容易。您可以以某种方式将其分成小梯形并对其进行近似,但它根本不是内置的。
  • 是的,我对缺少完成此操作的文档感到惊讶——不是针对sketch.js,而是针对canvas。哦,好吧——谢谢。
  • 当你说“像素数”时,你的意思是你想知道它的边界框的大小还是你真的想知道不透明像素的数量?
  • 不透明像素的字面量。
  • @markE,这不是一回事吗?

标签: jquery html html5-canvas


【解决方案1】:

这是计算画布上不透明像素的方法

// get a reference to your canvas
var c=document.getElementById("canvas");
var ctx=c.getContext("2d");

// get the pixel data from the canvas
var imgData=ctx.getImageData(0,0,c.width,c.height);

// loop through each pixel and count non-transparent pixels
var count=0;
for (var i=0;i<imgData.data.length;i+=4)
  {
      // nontransparent = imgData.data[i+3]==0
      if(imgData.data[i+3]==0){ count++; }
  }

[已编辑:获取画布上“如果填充”闭合形状的像素数]

我通常使用此代码在画布中进行蒙版,但我已在此处对其进行了调整,以获取闭合形状内的像素数。

一些caviats:

由于使用了“相邻”算法,未弯曲形状的笔划宽度必须至少为 2 像素,包含曲线的形状必须至少为 3 像素。

因为 Canvas 会自动绘制带有抗锯齿的笔触,所以内部像素数总是会比预期的稍大。这是因为抗锯齿“吞噬”了笔画,有效地导致了比预期更多的内部像素。顺便说一句,没有办法关闭画布的抗锯齿,如果你尝试 getImageData(),将所有形状像素设置为 rbg(0,0,0),putImageData(),生成的图像将也可以抗锯齿——只是锯齿状更多!

代码如下:

<!DOCTYPE html>
<html>
  <head>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
    canvas{border:1px solid red;}
</style>

<script>
$(function(){

//  The floodFill algorithm below is based on the good work by William Malone, Copyright 2010 William Malone (www.williammalone.com) -- Apache License: http://www.apache.org/licenses/LICENSE-2.0 -- Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

    var canvas=document.getElementById("canvas");
    var context = canvas.getContext("2d");
    var canvasWidth = canvas.width;
    var canvasHeight = canvas.height;
    var strokeColor =  {r: 0, g: 0, b: 0};
    var fillColor =  {r: 101,g: 155,b: 65};
    var fillData;
    var strokeData;

    function redraw() {
        context.clearRect(0, 0, canvasWidth, canvasHeight);
        context.putImageData(fillData, 0, 0);
        drawOutline(context);
    }

    function matchstrokeColor(r, g, b, a) {
      // must check for near black because of anti-aliasing
      return (r + g + b < 100 && a === 255);  
    }

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

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

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

      r = fillData.data[pixelPos];
      g = fillData.data[pixelPos + 1];
      b = fillData.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 === fillColor.r && g === fillColor.g && b === fillColor.b) {
        return false;
      }

      return true;
    }

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

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

      var newPos;
      var x;
      var y;
      var   pixelPos;
      var   neighborLeft;
      var   neighborRight;
      var   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 >= 0 && matchStartColor(pixelPos, startR, startG, startB)) {
          y -= 1;
          pixelPos -= canvasWidth * 4;
        }

        pixelPos += canvasWidth * 4;
        y += 1;
        neighborLeft = false;
        neighborRight = false;

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

          setPixel(pixelPos, fillColor.r, fillColor.g, fillColor.b);

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

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

          pixelPos += canvasWidth * 4;
        }
      }
    }

    // Fill
    function paintAt(startX, startY) {

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

      if (r === fillColor.r && g === fillColor.g && b === fillColor.b) {
        // this one's already filled
        return;
      }

      if (matchstrokeColor(r, g, b, a)) {
        return;
      }

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

      redraw();
    }


    function init() {

      var theShapes=document.getElementById("theShapes");
      var theShapesContext=theShapes.getContext("2d");
      drawOutline(theShapesContext);

      drawOutline(context);

      strokeData = context.getImageData(0, 0, canvasWidth, canvasHeight);
      context.clearRect(0, 0, context.canvas.width, context.canvas.height);
      fillData = context.getImageData(0, 0, canvasWidth, canvasHeight);

      $('#canvas').mousedown(function (e) {
        // Mouse down location
        var mouseX = e.pageX - this.offsetLeft;
        var mouseY = e.pageY - this.offsetTop;
        paintAt(mouseX, mouseY);
      });

      redraw();
    };

    function drawOutline(theContext){   
        theContext.beginPath();
        theContext.moveTo(55, 60);
        theContext.bezierCurveTo(35,  70,  35, 95,  85, 95);
        theContext.bezierCurveTo( 95,110, 130,110, 140, 95);
        theContext.bezierCurveTo(180, 95, 180, 80, 165, 70);
        theContext.bezierCurveTo(185, 40, 155, 35, 140, 45);
        theContext.bezierCurveTo(130, 25,  95, 30,  95, 45);
        theContext.bezierCurveTo( 70, 25,  45, 30,  55, 60);
        theContext.closePath();
        theContext.rect(200,30,100,70);
        theContext.lineWidth = 3;
        theContext.strokeStyle = 'rgb(0,0,0)';
        theContext.stroke();        
    }

    function getFilledPixelCount(theContext,theCanvas){
        // get the pixel data from the fill canvas
        var imgData=theContext.getImageData(0,0,theCanvas.width,theCanvas.height);
        console.log(imgData.data.length);
        var count=0;
        for (var i=0;i<imgData.data.length;i+=4){
              r = imgData.data[i],
              g = imgData.data[i + 1],
              b = imgData.data[i + 2],
              a = imgData.data[i + 3];
              if (r === fillColor.r && g === fillColor.g && b === fillColor.b) {
                  count++;
              }
          }
        return(count);
    }

    $("#counter").click(function(){alert("There are "+getFilledPixelCount(context,canvas)+" filled pixels.");});

    init();

}); // end $(function(){});

</script>

  </head>
  <body>
        <p>The original stroked shapes</p>
        <canvas id="theShapes" width=350 height=150></canvas><br/>
        <p>The filled shapes used for pixel counting</p>
        <p>Click inside a shape below</p>
        <canvas id="canvas" width=350 height=150></canvas><br/>
        <button id="counter">Filled Count</button>
  </body>
</html>

【讨论】:

  • 当然这不是封闭的区域,只是所有不透明的像素……
  • @Rich Bradshaw——很高兴见到你!好吧,如果形状已填充,它将计算填充,如果未填充,则不会计算填充 - 只是笔划:) 但是,如果 OP 进一步请求包含“if-filled”的像素计数未填充的形状——我也有一个解决方案。它有点复杂,但它基本上是边缘检测,涉及识别笔画之外的所有像素,然后从画布的总像素数中减去该数。
  • 听起来很有趣——看演示会很酷,我经常想知道如何进行边缘检测,但大多数示例都超级复杂!
  • 嗨@mar​​kE,感谢您发布此信息。我误解了你最初的问题——你能发布如何计算未填充形状轮廓内的像素吗?
  • 我已经发布了一个编辑过的答案来计算你的“if-filled”像素数:)
猜你喜欢
  • 2013-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多