【问题标题】:HTML5 Canvas - Filling a shape with random pixel colors?HTML5 Canvas - 用随机像素颜色填充形状?
【发布时间】:2013-06-27 05:28:50
【问题描述】:

我有一个形状,比如说:

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 个渐变的蓝色,并希望随着时间的推移随机绘制形状中包含的像素。


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

我还认为重绘每一帧可能会影响性能。


任何想法我将如何继续这件事?谢谢! :)

【问题讨论】:

    标签: javascript html canvas kineticjs


    【解决方案1】:

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

    问题:

    • 您需要一个 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>
    

    【讨论】:

    • 哇!这真是太棒了!这正是我想要做的。阅读您的答案,我也学到了很多东西。谢谢。
    【解决方案2】:

    如果你想知道画布中像素的 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 有点贵,但是如果您要处理渐变,这似乎是我找到的最快的路线。

    【讨论】:

      【解决方案3】:

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

      编辑: 我做了一些搜索,this algorithm 基本上是我提出的。

      【讨论】:

        猜你喜欢
        • 2016-02-20
        • 2014-05-21
        • 1970-01-01
        • 1970-01-01
        • 2016-12-27
        • 1970-01-01
        • 1970-01-01
        • 2014-05-26
        • 1970-01-01
        相关资源
        最近更新 更多