admin管理员组

文章数量:1420095

I have a shape, let's say:

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);

The shape will likely be different every time.

I have 10 gradients of blue and would like to randomly paint pixels contained within the shape as time goes on.


I have no idea how to even get the list of pixels contained within the shape, so that I could edit these.

Also I reckon that it may be a performance hit to redraw this each frame.


Any ideas how I would go on about this? Thanks! :)

I have a shape, let's say:

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);

The shape will likely be different every time.

I have 10 gradients of blue and would like to randomly paint pixels contained within the shape as time goes on.


I have no idea how to even get the list of pixels contained within the shape, so that I could edit these.

Also I reckon that it may be a performance hit to redraw this each frame.


Any ideas how I would go on about this? Thanks! :)

Share Improve this question edited Jun 29, 2013 at 15:20 Siot 951 silver badge6 bronze badges asked Jun 29, 2013 at 15:16 RadiantHexRadiantHex 25.6k47 gold badges155 silver badges251 bronze badges
Add a ment  | 

3 Answers 3

Reset to default 5

Your solution is actually a bit plex because of the problem below!

Problem:

  • You need a KineticJS shape object to draw your custom shape.
  • The Shape will only allow 1 single context.beginPath in its drawFunc.
  • Html Canvas only allows you to set 1 fillStyle (fill color) for each beginPath.
  • That means you’re limited to filling with 1 single blue color—not your 10 shades of blue.

Solution:

  • You can make your shape a clipping region.
  • Then use another background canvas showing through your clipped shape.
  • The background canvas will be used to efficiently draw individual pixels in all your shades of blue!

To draw random pixels and still cover all pixels, create a "map" array:

  • Create an array with the index# of every pixel on the stage. ( randomPixels[] )
  • Randomize the array.
  • Use the randomized array containing every pixel index to draw random pixels 1-at-a-time.

This is the function to create the map-array:

  // 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;
  }

Next create the custom KineticJS Shape:

  • Define your custom shape as a clipping region (in drawFunc)
  • DrawImage the background canvas (which will contain constantly updating pixels)
  • Since it’s clipped, the background canvas will only be visible inside your custom shape.

This creates a custom Kinetic Shape using your shape as a clipping path to a canvas background

        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
        });

To animate your 10 colors into your shape pixel by pixel

  • Incrementally fill the background canvas with your colored pixels.
  • Since the background canvas is “built-into” the kinetic Shape, all you have to do is draw a pixel on the canvas and then call layer.draw to make the change visible in your shape.

This function adds batches of randomly placed pixels to the background using 10 blue colors

  // 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();
      }
  }

A Note: to keep performance high---and also to keep viewer’s patient(!), I draw a batch of pixels at once.

The proper mix of blue shades is maintained.

Of course, you would style to your tastes.

Here is code and a Fiddle: http://jsfiddle/m1erickson/zhatS/

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Prototype</title>
    <script type="text/javascript" src="http://code.jquery./jquery.min.js"></script>
    <script src="http://d3lp1msu2r81bx.cloudfront/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>

well if you want to know the x,y of the pixels in the canvas you can use a loop like the following:

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
  }
}

if x/y positioning isn't important you can reduce it to a single loop (same setup as before).

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
}

then you want to re-paint the image to the canvas.

context.putImageData(image, 0, 0);

putImageData is a little expensive however if you are manipulating gradients this seems to be the quickest route I have found.

I don't know how familiar are you with graph search, but either Breadth first search or Depth first search might help you here. All you need to know is one starting pixel that is 100% in the shape.

Edit: I have done some search and this algorithm is basically the one I have proposed.

本文标签: javascriptHTML5 CanvasFilling a shape with random pixel colorsStack Overflow