Welcome back

Starting off today we are going to take a brief look at gradients. After that we are going to be looking at altering individual pixels. If you are not sure what a gradient is check out this. Once your done looking that over let's get right to it.

Gradients

Linear Gradients

Now because the canvas does not draw individual objects and is just basically a static painting, gradients do not get drawn on the shapes themselves. Instead the gradient is created in relation to the entire canvas. So the positioning of the gradient must be in keeping with your coords for what ever shape you are filling. We will be starting with linear gradients. They to me are the easiest to work with and the easiest to show how having the correct coords is important.

  // Create our gradients and save to a variable
var grad1 = ctx.createLinearGradient(startX, startY, endX, endY);

// Add the color stops
// Color stops can be added inbetween 0 and 1 at any interval.
// 0 represents the begining of the gradient while 1 the end
// if we were to add another color stop at 0.5 that color would 
// then begin halfway into our gradient.
// 
grad1.addColorStop(0,"#F00");
grad1.addColorStop(1,"#0F0");

// Use our gradient as a fill
ctx.fillStyle = grad1;
ctx.fillRect(W/2-50,H/2-50,100,100);


In this case I centered my rectangle and my gradient had a startx of W/2 - 100 px that puts the x coord start of the gradient 50 pixels to the left of my rectangle. I had the y start 50px above the middle exactly my end x 50 pixels to the right of my rectangle and my end y 50 pixels below it.

A visual representation of this would be

gradient example

In which the only visible portion of the gradient is inside the black box.

If we were to take and set the gradient strictly to the size of our rectangle we then see the entire gradient.

So let's make a linear gradient. I want you to try this one on your own. It should

  • be 100px by 100px

  • go from the right side middle of you rectangle to the left side

  • have 3 color stops

Hmmm mine kind of looks like a rocket pop. As you can see it's pretty easy. just think of it as cutting a shape out of a piece of paper putting it on top of another paper with rainbow colors and moving it around.

Radial Gradients

Similar to the way that linear works there is another gradient, radial gradient. It works the same as linear gradients with the exception that you need to add in a radius to both the start and end positions.

  var grad = ctx.createRadialGradient(startX, startY, startRadius, endX, endY, endRadius);


To be honest radial gradients are great for making glowing particles but that is another story for another day. Don't worry we will get there I love particle systems they are so much fun.

Patterns

The last fillStyle we will work on is going to be patterns. It isn't so much a gradient but we will be putting it here anyway. This will be using a photo and repeating to create a patterned background effect. We will be using this asset. As you can see

grass.png

it is really really small. That's ok. For something like this we don't need huge. So start out with creating your image object. If you don't remember how that's ok. Just go to the previous section, I garuntee you that it is in there. Now in our onload function we want to set our gradient to a variable then our fillStyle to that variable then draw our rectangle. For this we are going to draw our rectangle the size of our canvas.

    grass.onload = function(){
  var pattern = ctx.createPattern(grass,"repeat");
  ctx.fillStyle = pattern;
  ctx.fillRect(0,0,W,H);
}

And voila a field of grass.

Manipulating Image data

The last thing we will be working on today is altering image data. So let's go over a few things. First the way image data is stored. It is one giant array. Seriously, if you have an image that is 100x100 pixels, the array is 800 places long. Well from 0 to 799. So that is the first thing to realize. The length of the array that holds all the data is width(100) times height(also 100) times 4. Times 4? Well yes because that array (instead of doing multidementional arrays or the sort) is made up of the color of all the pixels broken into rgba format. So when you are going through the array the first pixel will be Index 0 - 3. I.E. lets say we have an array with image data called steve. steve[0] would be the red value in the first pixel. steve[4] would also be a red value but for the second pixel. This pattern keeps going until we get to our width. Then the array loops back to the left side of the image data and starts again. Until we get to the height of our image. So if our photo is only 4 pixels wide and 2 pixels tall the 5th pixel (indexs 16-19) would actually be the far left pixel on the second row of pixels.

pixeldataimage

Well what does that mean. Well it means that knowing how our array is structured we can loop through it and pick out specific color hues. For example if we wanted to alter only the red in our image it would be every 4 numbers in our array. Indexes 0,4,8,12,16,20,24,28....... and so on and so forth. If we wanted to alter all the blue it is the same thing. Indexes 2,6,10,14,18,22,26,30........ and so on. So let's build a 100x100 red box using only image data.

Now first we have to create our image data like so

  var myImg=ctx.createImageData(100/*This is width*/,100/*This is the height*/);

Then we loop through our array and set each pixel the way we want. To access the array we call myImg.data then we can either use the .length() method or we could take the height time width time 4 to get the length of the array. We will do the .length() method because it is easier and if we make a function at some point for this we need to get used to abstracting things. Now by default a new data image is set to rgba(0,0,0,0) or transparent black. So we will need to not only turn our red red but also turn our opacity up as well. Since both of these values can range from 0 to 255 and we want this a red square let's set them to 255.

  for (var i=0;i< myImg.data.length;i+=4)
  {
  myImg.data[i]=255; //Because we start at 0 and increment i +4 every time i will always be red 
  myImg.data[i+3]=255; // if we index i + 3 on each loop that will be our alpha 
  }

Lastly we must place the image onto our canvas. We will do this with the putImageData() method passing in our myImg variable and the position we want the image to go. Just like this:

  ctx.putImageData(myImg, W/2 - 50, H/2-50);

and that will give us a red square in dead center of our canvs.

Now we can use getImageData() to manipulate anything on our canvas. Including other shapes. All we need is the x and y position of the shape and the width and height of the shape. The most obvious thing we can work with though is an image. Note when working with the getImageData() method the image must be a local file to the server the javascript file is stored on. i.e. you cannot use a url of a file on instagram to do this.. The technique is to interate over all the image data and alter the value of each color part then put the image data on the canvas like we did before

  function filtered(r,g,b,a){
        ctx.drawImage(this, x, y);
        var myImg = ctx.getImageData(x, y, w, h);

        for (var i = 0; i < myImg.data.length; i += 4){
            myImg.data[i] += r;
            myImg.data[i + 1] += g;
            myImg.data[i + 2] += b;
            myImg.data[i + 3] += a;
        } 

        ctx.putImageData(myImg, x, y);
   }

Now I took this and wrapped it up in a function prototyped to the image object. You don't really have to but prototyping will be something that we will be doing for particle systems later on.

Because of the limitations with image data I will not be able to do a live pen showing you how to filter an image. Instead I have made a page for it so you can play around with the values. I have also added the ability to set the photo to black and white and to invert the colors. If you want to see the pure JS you can find it here and the live page for it can be found here. Black and whiite is done by altering the brightness level of the original color data. First we find out the luminiosity(brightness) of each pixel by finding the sum of each color channel multiplied by a wieghted amount. The formula is Y = 0.2126*Red + 0.7152*Green + 0.0722*Blue where Y is the luminosity. To break that down. We have an easier time percieving green colors a decent for red and blue is a little hard for us to percieve which is why each color is weighted differently. We then add that together and set each color channel to the same value which evens the colors out and makes it a gray value. Think about any time you set a color in rgb to something like #AAA. It is always a greyscale right. No color is stronger than any other which is effectivly the way white light works. You would program it like so

      for(i = 0; i < myImg.data.length; i++){
        var brightness = 0.2126 * myImg.data[i] + 0.7152 * myImg.data[i + 1] + 0.0722 * myImg.data[i + 2];
        myImg.data[i]  = brightness;
        myImg.data[i + 1] =  brightness;
        myImg.data[i + 2] = brightness; 
}

For inverted colors it is the same thinking. Each color is a value added to black. rgb(255,0,0) is at the maximum value for red going from black. So what is the opposite. That same value subtracted from white.

      for(i = 0; i < myImg.data.length; i++){
        myImg.data[i]  = 255 - myImg.data[i];
        myImg.data[i + 1] = 255 - myImg.data[i + 1];
        myImg.data[i + 2] = 255 - myImg.data[i + 2];    
}

Notice how we don't touch the alpha channel in either of these. Opacity really has nothing to do with it.

Last things to cover today are toDataUrl and saving the canvas as an image. You can also use AJAX and a data url as a src for an image object but I will not be going into that. Now saving images and dataUrls have the same short commings of getting image data. Has to be on the same server. To burn through both saving and converting your canvas to a data Url just set a variable to canvas.toDataURL() and set the canvas.src to that variable. Like so

  var URL = canvas.toDataURL();
canvas.src = URL;


that will make the whole canvas to be able to be saved as a png image. If you want say jpeg then pass image/jpg as an argument into toDataURL().

Anyway that is all for today. Next week we will be working on transformations.


1,448 0 0