Home > Experimental, Front End Development, HTML, Javascript, Labs > Canvas Image Manipulation Experiment

Canvas Image Manipulation Experiment

September 9th, 2011


There are plenty of cool things that HTML 5 has to offer. Here lies an experiment with the canvas and image manipulation. Oh what FUN!

Canvas Experiment (Best viewed on Chrome / Safari)

Just a brief description of what I built. We first pull in all the image data, and convert them to particles. Then let the playing happen. In this case I just made the particles jump away from the mouse, then jump back. I heavily commented the code to help you look through it.

Canvas Image Manipulation

Does not work locally! Image needs to be on server somewhere.

If you try to load in a local image or an image from another domain you will get a javascript security error. To get around this you can encode the image into base64. Here is a handy tool for that….very simple to use.

Play with the code on JSFiddle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="UTF-8">
  <title>Canvas Image</title>
  <style>
  body{
    background:black;
  }
    canvas{
      width:900px;
      height:200px;
      background:none;
    }
    #canvasSource{
      display:none;
    }
  </style>
</head>
<body>
   
<!-- HTML Code -->
<img id="canvasSource" src="picture.png" alt="Canvas Source" />

<canvas id="area" width="900" height="200"></canvas>

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" ></script>

<script>
//Image Manipulation Javascript will go here.
</script>

</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
window.onload = function () {
   
    /**
    * Checks if a pixel is white or not
    * @param {Object} pixel A pixel with an rgb value
    * @returns Is the pixel white or not.
    * @type Boolean
    */

    function isPixelWhite (pixel) {
        return (pixel.r > 50 && pixel.g > 50 && pixel.b > 50);
    }

    /**
    * Converts an rgb value to hex
    * @param {Number} R The red value
    * @param {Number} G The green value
    * @param {Number} B The blue value
    * @returns A hex value based on the rgb given
    * @type String
    */

    function rgbToHex(R,G,B) {
        return toHex(R)+toHex(G)+toHex(B);
    }

    /**
    * convert value from 0-255 to hex.
    * @param {Number} n Number between 0 and 255
    * @returns Hex value
    * @type String
    */

    function toHex(n) {
       n = parseInt(n,10);
       if (isNaN(n)) {
           return "00";
       }
       n = Math.max(0,Math.min(n,255));
       return "0123456789ABCDEF".charAt((n-n%16)/16) + "0123456789ABCDEF".charAt(n%16);
    }
   
    /**
    * Extends an array with the given options
    * @param {Object} obj The main object
    * @param {Object} extObj The extending object
    * @returns The merged objects
    * @type Object
    */

    var extend = function(obj, extObj) {
       var i;
       for (i in extObj) {
           if (extObj[i]) {
               obj[i] = extObj[i];
           }
       }

       return obj;
    };
   
   
    /**
    * Pixel Class
    * @author Shawn
    */

    var Particle = function(options){
       
        //Default Values
        var params = {color:"#fff",x:0, y:0,width:1, height:1, angle:0, speed:3 };
        params = extend(params,options);
       
        //Init Variables
        this.color = params.color;
        this.x = params.x;
        this.y = params.y;
        this.originalX = this.x;
        this.originalY = this.y;
        this.width = params.width;
        this.height = params.height;
        this.angle = params.angle * ( Math.PI / 180 );
        this.speed = params.speed;
    };
   
    /**
    * Sets x,y value based on current angle.
    * @returns
    * @type Void
    */

    Particle.prototype.getVector = function (){
      this.x = Math.cos(this.angle);
      this.y = Math.sin(this.angle);
    };
   
    /**
    * Sets the current angle
    * @param {Number} angle Angle to set it at
    * @returns Angle given, but rounded
    * @type Number
    */

    Particle.prototype.setAngle = function(angle){
       
        //Convert to radians, so it's useful.
        this.angle = angle * ( Math.PI / 180 );
        return angle | 0;
    };
   
    /**
    * Gets the current angle
    * @returns The current angle
    * @type Number
    */

    Particle.prototype.getAngle = function(){
        //Converts angle from radians to angle then rounds it.
        return (this.angle / ( Math.PI / 180 )) | 0;
    };
   
   
    /**
    * Emits particles
    * @author Shawn
    * @requires Particle
    */

    var Emitter = function (){
       
        //Initial values.
        this.maxParticles = 20000;
        this.particles = [];
        this.active = true;
        this.x = 0;
        this.y = 0;
    };
   
    /**
    * Adds a particle to the emitter
    * @param {Object} options Particle options
    * @returns The particle just added
    * @type Particle
    */

    Emitter.prototype.addParticle = function (options) {
       
        //Make sure there is less particles than the max amount
        if (this.particles.length < this.maxParticles) {
           
            //Set particle default values.
            var params = {color:"#fff",x:this.x,y:this.y, width:1,height:1};
            params = extend(params,options);

            //Add particle to emitter.
            var p = new Particle(params);
            p.id = this.particles.length;
            this.particles.push(p);

            return p;
        }
    };
   
    /**
    * Gets a particle at a given identifier
    * @param {Number} id Particles identifier
    * @returns A particle
    * @type Particle
    */

    Emitter.prototype.getParticle = function (id) {
        return this.particles[id];
    };
   
    /**
    * Get how many particles there are.
    * @returns Number of particles
    * @type Number
    */

    Emitter.prototype.getLength = function() {
      return this.particles.length;
    };
   
   /**
   * Paul Irish's method for requesting animation frame
   * @returns Animation Frame
   * @type Function
   */

    window.requestAnimFrame = (function(){

      return  window.requestAnimationFrame       ||
              window.webkitRequestAnimationFrame ||
              window.mozRequestAnimationFrame    ||
              window.oRequestAnimationFrame      ||
              window.msRequestAnimationFrame     ||
              function(/* function */ callback, /* DOMElement */ element){
                window.setTimeout(callback, 1000 / 60);
              };
    })();

    //Setup canvas.
    var canvas = document.getElementById("area");
    var ctx = canvas.getContext("2d");
    var image = document.getElementById("canvasSource");
    ctx.drawImage(image, 0, 0);
   
    //get Image data.
    var imgData = ctx.getImageData(0,0, 900, 200);
   
    var pixels = [];
    var init = false;
    var mouseX, mouseY = 0;
    var affecetedArea = 70;
   
    var emit = new Emitter();
    var i;
   
    //Create a particle based on the image data.
    //Image data is ordered like r,g,b,a,r,g,b,a...need to count by 4
    for (i=0; i < imgData.data.length; i+=4) {
        var pixel = {};
        pixel.r = imgData.data[i];
        pixel.g = imgData.data[i+1];
        pixel.b = imgData.data[i+2];
        pixel.index = i / 4;
       
        //Setup columns and rows
        pixel.x = pixel.index % 900;
        pixel.y = Math.floor(pixel.index / 900);

        //The white pixels are the only ones we want to animate.
        if (isPixelWhite(pixel)) {
           
            //Pick a random angle.
            var angle = (Math.random() * 360);
           
            emit.addParticle({color:"#"+rgbToHex(pixel.r,pixel.g,pixel.b) ,x: pixel.x, y: pixel.y, angle:angle });
        }
    }
   
    /**
    * Mouse Move Handler...set the mouseX and MouseY variables
    * @param {Object} e Event Object
    * @returns Nothing
    * @type Void
    */

    $('#area').mousemove(function(e) {

        if(e.offsetX) {
            mouseX = e.offsetX;
            mouseY = e.offsetY;
   
        }else if(e.layerX) {
            mouseX = e.layerX;
            mouseY = e.layerY;
        }

    });
   
   
    //Start animation loop.
    function animate() {
      requestAnimFrame( animate );
      draw();
    }
   
    animate();
   
    var oldx,oldy,newx,newy;
   
    /**
    * Draws each frame
    * @returns Nothing
    * @type Void
    */

    function draw() {
        var i;
        canvas.width = 900;
        canvas.height = 200;
       
        //Render each particle in the emitter.
        for (i=0; i < emit.getLength(); i++) {

            var p = emit.getParticle(i);
           
            //Calculate the distance of the particle from the mouse.
            var distx = p.x - mouseX;
            var disty = p.y - mouseY;
            var distance = Math.sqrt(distx*distx + disty*disty);

            //If it's within the affected area.
            if (distance < affecetedArea) {
               
                //Push the particle out of the way.
                //p.x -= (Where the particle currently is - Where you want the particle to end up) / speed
                p.x -= (p.x - (mouseX + affecetedArea * distx / distance)) / 5;
                p.y -= (p.y - (mouseY + affecetedArea * disty / distance)) / 5;
               
            }else{
               
                //Return the particle to it's original place.
                if (p.x != p.originalX || p.y != p.originalY) {
                    p.x -= (p.x - p.originalX) / 2;
                    p.y -= (p.y - p.originalY) / 2;
                }

            }
           
            //Draw the particle
            ctx.fillStyle = p.color;
            ctx.fillRect(p.x,p.y,p.width,p.width);
        }
    }
   

};



Top