3

我有一个形状,比方说:

var context = canvas.getContext();
context.beginPath();
context.moveTo(200, 50);
context.lineTo(420, 80);
context.quadraticCurveTo(300, 100, 260, 170);
context.closePath();
canvas.fillStroke(this);

形状可能每次都不一样。

我有 10 个蓝色渐变,并且希望随着时间的推移随机绘制形状中包含的像素。


我什至不知道如何获取形状中包含的像素列表,以便我可以编辑这些。

此外,我认为重绘每一帧可能会影响性能。


有什么想法我会怎么做?谢谢!:)

4

3 回答 3

5

由于以下问题,您的解决方案实际上有点复杂!

问题:

  • 您需要一个 KineticJS 形状对象来绘制您的自定义形状。
  • Shape 在其 drawFunc 中只允许 1 个 context.beginPath。
  • Html Canvas 仅允许您为每个 beginPath 设置 1 个填充样式(填充颜色)。
  • 这意味着您只能填充 1 种蓝色,而不是 10 种蓝色。

解决方案:

  • 您可以使您的形状成为剪切区域。
  • 然后使用另一个背景画布显示您剪切的形状。
  • 背景画布将用于有效地绘制所有蓝色阴影中的单个像素!

在此处输入图像描述

要绘制随机像素并仍然覆盖所有像素,请创建一个“地图”数组:

  • 使用舞台上每个像素的 index# 创建一个数组。(随机像素[])
  • 随机化数组。
  • 使用包含每个像素索引的随机数组一次绘制一个随机像素。

这是创建地图数组的函数:

  // make an array with elements from 0 to shapeWidth*shapeHeight in random order

  function initPixelArray(shapeWidth,shapeHeight){
      var array=[];
      var i;
      var countdown;
      var temp;

      // Must be a better way to fill an array with
      // a sequential group of numbers, just in random order
      // Anyone got some magic for this ????????????

      // fill array with sequential numbers representing each pixel;
      for(var i=0;i<shapeWidth*shapeHeight;i++){ array.push(i); }
      // randomize the array
      countdown=array.length;
      while (countdown > 0) {
          i = (Math.random() * countdown--) | 0;
          temp = array[countdown];
          array[countdown] = array[i];
          array[i] = temp;
      }
      //
      return array;
  }

接下来创建自定义 KineticJS 形状:

  • 将您的自定义形状定义为剪切区域(在 drawFunc 中)
  • DrawImage 背景画布(将包含不断更新的像素)
  • 由于它已被剪裁,因此背景画布将仅在您的自定义形状内可见。

这将使用您的形状作为画布背景的剪切路径创建自定义动力学形状

        var shape = new Kinetic.Shape({
          drawFunc: function(canvas){
              var context = canvas.getContext();
              context.beginPath();
              context.moveTo(0, 50);
              context.lineTo(220, 80);
              context.quadraticCurveTo(100, 100, 60, 170);
              context.closePath();
              context.clip();
              context.drawImage(this.backgroundCanvas,0,0);
              canvas.fillStroke(this);  
          },
          x: x,
          y: y,
          stroke: "lightgray",
          strokeWidth: 8,
          draggable:true
        });

将您的 10 种颜色逐个像素地动画到您的形状中

  • 用彩色像素逐步填充背景画布。
  • 由于背景画布“内置”在动态形状中,您所要做的就是在画布上绘制一个像素,然后调用 layer.draw 以使更改在您的形状中可见。

此函数使用 10 种蓝色将一批随机放置的像素添加到背景中

  // add pixels to the background canvas
  // after layer.draw the new pixels will be visible in the shape
  function updatePattern(){

      // add 10 pixels of each type of blue (100px total)
      for(var n=0;n<shapeWidth/8;n++){

          // set a blue color
          tempCtx.fillStyle=color[n];
          tempCtx.beginPath();

          // draw 10 random pixels in that blue
          for(var b=0;b<10;b++){

              // get next random pixel
              var i=randomPixels[nextPixel]
              // calculate x/y from i
              var y=Math.floor(i/shapeWidth);
              var x=(i-y*shapeWidth)-1;
              // draw
              tempCtx.rect(x,y,1,1);
              // increment array index and do bounds check
              if(++nextPixel>=randomPixels.length){return;}

          }
          // done filling with this blue--do the fill()
          tempCtx.fill();
      }
  }

注意:为了保持高性能——也为了保持观众的耐心(!),我一次绘制了一批像素。

保持蓝色色调的适当混合。

当然,你会根据自己的口味设计。

这是代码和小提琴:http: //jsfiddle.net/m1erickson/zhatS/

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Prototype</title>
    <script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
    <script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.5.1.min.js"></script>

<style>
#container{
  border:solid 1px #ccc;
  margin-top: 10px;
  width:300px;
  height:300px;
}
canvas{border:1px solid red;}
</style>        
<script>
$(function(){

    var stage = new Kinetic.Stage({
        container: 'container',
        width: 300,
        height: 300
    });
    var layer = new Kinetic.Layer();
    stage.add(layer);


    // the KineticJS shape
    var myShape;

    // 
    var shapeWidth=stage.getWidth();
    var shapeHeight=stage.getHeight();

    // temporary canvas to create/update the pattern for the Shape
    var tempCanvas=document.createElement("canvas");
    var tempCtx=tempCanvas.getContext("2d");
    tempCanvas.width=shapeWidth;
    tempCanvas.height=shapeHeight;

    // an array of pixel references from 0 to shapeWidth*shapeHeight
    // used to draw individual pixels randomly
    var randomPixels=initPixelArray(shapeWidth,shapeHeight);
    var nextPixel=0;

    // shades of colors to fill with
    var color;
    var colors={
      blues:["#0000ff","#2b60de","#4863a0","#2554c7","#000080","#afdcec","#c6deff","#6698ff","#c2dfff","#79baec"],
      reds:["#ff0000","#f4c3c2","#fd5c5c","#fe2625","#ff3030","#ff6666","#fa8072","#efc3bf","#ff6347","#ef7942"]
    }

    // Create a KineticJS shape
        function kShape(x,y){
          var shape = new Kinetic.Shape({
            drawFunc: function(canvas){
                var context = canvas.getContext();
                context.beginPath();
                context.moveTo(0, 50);
                context.lineTo(220, 80);
                context.quadraticCurveTo(100, 100, 60, 170);
                context.closePath();
                context.clip();
                context.drawImage(this.backgroundCanvas,0,0);
                canvas.fillStroke(this);  
            },
            x: x,
            y: y,
            stroke: "lightgray",
            strokeWidth: 8,
            draggable:true
          });
          // let the shape keep a reference to the background canvas
          shape.backgroundCanvas=tempCanvas;
          layer.add(shape);
          layer.draw();
          return(shape);
        }


    // make an array with elements from 0 to shapeWidth*shapeHeight in random order
    function initPixelArray(shapeWidth,shapeHeight){
        var array=[];
        var i;
        var countdown;
        var temp;

        // Must be a better way to fill an array with
        // a sequential group of numbers, just in random order
        // Anyone got some magic for this ????????????

        // fill array with sequential numbers representing each pixel;
        for(var i=0;i<shapeWidth*shapeHeight;i++){ array.push(i); }
        // randomize the array
        countdown=array.length;
        while (countdown > 0) {
            i = (Math.random() * countdown--) | 0;
            temp = array[countdown];
            array[countdown] = array[i];
            array[i] = temp;
        }
        //
        return array;
    }


    // add pixels to the pattern
    function updatePattern(){

        // add 10 pixels of each type of blue (100px total)
        for(var n=0;n<shapeWidth/8;n++){

            // set a blue color
            tempCtx.fillStyle=color[n];
            tempCtx.beginPath();

            // draw 10 random pixels in that blue
            for(var b=0;b<10;b++){

                // get next random pixel
                var i=randomPixels[nextPixel]
                // calculate x/y from i
                var y=Math.floor(i/shapeWidth);
                var x=(i-y*shapeWidth)-1;
                // draw
                tempCtx.rect(x,y,1,1);
                // increment array index and do bounds check
                if(++nextPixel>=randomPixels.length){return;}

            }
            // done filling with this blue--do the fill()
            tempCtx.fill();
        }
    }


    // great cross-browser animation shim by Paul Irish
    window.requestAnimFrame = (function(callback) {
      return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
      function(callback) {
        window.setTimeout(callback, 1000 / 60);
      };
    })();


    // animate filling of pixels
    var fps = 60;
    function animate() {
        setTimeout(function() {
            requestAnimationFrame(animate);

            if(nextPixel>=randomPixels.length){return;}

            updatePattern();

            layer.draw();

        }, 1000 / fps);
    }


    ////////////////// ACTION STARTS HERE

    var myShape=kShape(20,20);

    $("#blues").click(function(){ 
        color=colors.blues;
        nextPixel=0; 
        animate(); 
    });

    $("#reds").click(function(){ 
        color=colors.reds;
        nextPixel=0; 
        animate(); 
    });



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

</script>       
</head>

<body>
    <div id="container"></div>
    <button id="blues">Fill blues</button>
    <button id="reds">Fill reds</button>
</body>
</html>
于 2013-06-30T00:30:20.077 回答
3

好吧,如果您想知道画布中像素的 x,y,可以使用如下循环:

var image = context.getImageData(0, 0, width, height),
    data = image.data;
for( var x = 0; x < width; x++ ) {
  for( var y = 0; y < height; y++) {
    var i = (x*4)+(y*4*image.width);
    data[i] = ..; // I am the red
    data[i+1] = ..; // I am the green
    data[i+2] = ..; // I am the blue
    data[i+3] = ..; // I am the alpha
  }
}

如果 x/y 定位不重要,您可以将其简化为一个循环(与之前的设置相同)。

for( var i = 0; i < data.length; i+=4) {
  data[i] = ..; // I am the red
  data[i+1] = ..; // I am the green
  data[i+2] = ..; // I am the blue
  data[i+3] = ..; // I am the alpha
}

那么您想将图像重新绘制到画布上。

context.putImageData(image, 0, 0);

putImageData 有点贵,但是如果您正在操纵渐变,这似乎是我找到的最快路线。

于 2013-06-29T17:55:18.890 回答
-1

我不知道您对图形搜索有多熟悉,但广度优先搜索或深度优先搜索可能会对您有所帮助。您需要知道的只是一个 100% 形状的起始像素。

编辑:我做了一些搜索,这个算法基本上是我提出的。

于 2013-06-29T16:00:47.780 回答